├── .gitignore
├── .idea
├── .gitignore
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── jarRepositories.xml
├── kotlinScripting.xml
├── misc.xml
└── vcs.xml
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── example
│ │ └── play
│ │ └── HomeScreenTest.kt
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── example
│ │ └── play
│ │ ├── anim
│ │ ├── BottomNavAnim.kt
│ │ ├── FilterChipAnim.kt
│ │ ├── HeaderAnim.kt
│ │ ├── InstallButtonAnim.kt
│ │ └── RatingBarAnim.kt
│ │ ├── data
│ │ ├── AppRepo.kt
│ │ └── Models.kt
│ │ ├── theme
│ │ ├── Color.kt
│ │ ├── Shape.kt
│ │ ├── Theme.kt
│ │ └── Type.kt
│ │ ├── ui
│ │ ├── BottomNav.kt
│ │ ├── MainActivity.kt
│ │ ├── NavGraph.kt
│ │ ├── PlayApp.kt
│ │ ├── Tabs.kt
│ │ ├── apps
│ │ │ ├── Apps.kt
│ │ │ └── applist
│ │ │ │ ├── AppItem.kt
│ │ │ │ ├── ForYou.kt
│ │ │ │ └── TopCharts.kt
│ │ ├── books
│ │ │ └── Books.kt
│ │ ├── components
│ │ │ ├── AppBarLayout.kt
│ │ │ ├── Card.kt
│ │ │ ├── Divider.kt
│ │ │ ├── Filters.kt
│ │ │ ├── Gradient.kt
│ │ │ ├── Image.kt
│ │ │ ├── PlayToolBar.kt
│ │ │ ├── Scaffold.kt
│ │ │ ├── Surface.kt
│ │ │ ├── SwitchComponent.kt
│ │ │ └── progressindicator
│ │ │ │ └── ProgressIndicator.kt
│ │ ├── details
│ │ │ ├── AppDetails.kt
│ │ │ ├── ScreenshotItem.kt
│ │ │ ├── about
│ │ │ │ └── About.kt
│ │ │ ├── header
│ │ │ │ └── Header.kt
│ │ │ ├── installbutton
│ │ │ │ ├── animated
│ │ │ │ │ ├── AnimatedInstallButton.kt
│ │ │ │ │ ├── ButtonContent.kt
│ │ │ │ │ └── InstallButtonLayout.kt
│ │ │ │ └── plain
│ │ │ │ │ └── PlainInstallButton.kt
│ │ │ ├── reviews
│ │ │ │ ├── RatingsAndReviews.kt
│ │ │ │ └── ReviewItem.kt
│ │ │ ├── screenshots
│ │ │ │ └── Screenshots.kt
│ │ │ └── stats
│ │ │ │ └── Stats.kt
│ │ ├── games
│ │ │ └── Games.kt
│ │ └── movies
│ │ │ ├── Movies.kt
│ │ │ └── movielist
│ │ │ ├── MovieItem.kt
│ │ │ ├── MoviesForYou.kt
│ │ │ └── TopSelling.kt
│ │ └── utils
│ │ ├── Navigation.kt
│ │ └── SystemUi.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── bg_black_rect.xml
│ ├── ic_apps.xml
│ ├── ic_books.xml
│ ├── ic_books_otlined.xml
│ ├── ic_box.xml
│ ├── ic_download.xml
│ ├── ic_games.xml
│ ├── ic_launcher_background.xml
│ ├── ic_movies.xml
│ ├── ic_movies_outlined.xml
│ ├── ic_star_solid.xml
│ └── user_profile_pic.jpg
│ ├── font
│ ├── product_sans_bold.ttf
│ └── product_sans_regular.ttf
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── raw
│ └── working.json
│ ├── values-night
│ └── themes.xml
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── themes.xml
├── build.gradle
├── buildSrc
├── build.gradle.kts
├── build
│ ├── classes
│ │ └── kotlin
│ │ │ └── main
│ │ │ ├── META-INF
│ │ │ └── buildSrc.kotlin_module
│ │ │ └── com
│ │ │ └── example
│ │ │ └── play
│ │ │ └── buildsrc
│ │ │ ├── Libs$Accompanist.class
│ │ │ ├── Libs$AndroidX$Compose.class
│ │ │ ├── Libs$AndroidX$Test$Ext.class
│ │ │ ├── Libs$AndroidX$Test.class
│ │ │ ├── Libs$AndroidX.class
│ │ │ ├── Libs$Coroutines.class
│ │ │ ├── Libs$Kotlin.class
│ │ │ ├── Libs.class
│ │ │ └── Versions.class
│ ├── kotlin
│ │ └── buildSrcjar-classes.txt
│ ├── libs
│ │ └── buildSrc.jar
│ ├── pluginUnderTestMetadata
│ │ └── plugin-under-test-metadata.properties
│ ├── reports
│ │ └── plugin-development
│ │ │ └── validation-report.txt
│ └── tmp
│ │ └── jar
│ │ └── MANIFEST.MF
└── src
│ └── main
│ └── java
│ └── com
│ └── example
│ └── play
│ └── buildsrc
│ └── Dependencies.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
├── demo.gif
├── screenshot_1.png
├── screenshot_2.png
├── screenshot_3.png
├── screenshot_4.png
├── screenshot_5.png
└── screenshot_6.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/.idea/kotlinScripting.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 2147483647
6 | true
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JetStore
2 |
3 | A clone of Google PlayStore app with animations in Android built using Jetpack compose.
4 |
5 |
10 |
11 |
31 |
32 | ### This sample app showcases the following:
33 |
34 | * Working with bottom nav bar and tabs
35 | * Implementation of Chips to filter
36 | * Light and dark theme using custom color palette
37 | * Horizontal and vertical scrollable list
38 | * Using Animation APIs
39 | * Test compose layouts
40 |
41 | ### Screens
42 | Demo UI | Games | App Details
43 | :-------------------------:|:-------------------------: | :-------------------------:
44 |
|
|
45 |
46 | App Reviews | Apps (Dark) | Movies (Dark)
47 | :-------------------------:|:-------------------------: | :-------------------------:
48 |
|
|
49 |
50 | ### Status: 🚧 In progress
51 | JetStore is still under development and some screens are yet to be designed.
52 |
53 | ### Libraries used
54 |
55 | * [Jetpack Compose]
56 | * [Accompanist]
57 | * [Coroutines]
58 | * [Lottie Compose]
59 | * [Compose Navigation]
60 | * [Compose Test]
61 |
62 | [Jetpack Compose]: https://developer.android.com/jetpack/compose
63 | [Accompanist]: https://github.com/chrisbanes/accompanist
64 | [Coroutines]: https://developer.android.com/kotlin/coroutines
65 | [Lottie Compose]: https://github.com/airbnb/lottie
66 | [Compose Navigation]: https://developer.android.com/jetpack/compose/navigation
67 | [Compose Test]: https://developer.android.com/jetpack/compose/testing
68 |
69 | ### How to get started
70 | Please get Android Studio Arctic Fox 2020.3.1 Beta 3 or above to build this project
71 | from [from here](https://developer.android.com/studio/preview/).
72 |
73 | ### License
74 | ```
75 | Copyright 2020 Pushpal Roy
76 |
77 | Licensed under the Apache License, Version 2.0 (the "License");
78 | you may not use this file except in compliance with the License.
79 | You may obtain a copy of the License at
80 |
81 | http://www.apache.org/licenses/LICENSE-2.0
82 |
83 | Unless required by applicable law or agreed to in writing, software
84 | distributed under the License is distributed on an "AS IS" BASIS,
85 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
86 | See the License for the specific language governing permissions and
87 | limitations under the License.
88 | ```
89 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | import com.example.play.buildsrc.Libs
2 |
3 | plugins {
4 | id 'com.android.application'
5 | id 'kotlin-android'
6 | id 'kotlin-android-extensions'
7 | }
8 |
9 | android {
10 | compileSdkVersion 30
11 | buildToolsVersion "30.0.3"
12 |
13 | defaultConfig {
14 | applicationId "com.example.play"
15 | minSdkVersion 21
16 | targetSdkVersion 30
17 | versionCode 1
18 | versionName "1.0"
19 | vectorDrawables.useSupportLibrary true
20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 | kotlinOptions {
34 | jvmTarget = '1.8'
35 | useIR = true
36 | }
37 | buildFeatures {
38 | compose true
39 | // Disable unused AGP features
40 | buildConfig false
41 | aidl false
42 | renderScript false
43 | resValues false
44 | shaders false
45 | }
46 | composeOptions {
47 | kotlinCompilerVersion Libs.Kotlin.version
48 | kotlinCompilerExtensionVersion Libs.AndroidX.Compose.composeVer
49 | }
50 | packagingOptions {
51 | // https://github.com/Kotlin/kotlinx.coroutines/issues/2023
52 | exclude 'META-INF/AL2.0'
53 | exclude 'META-INF/LGPL2.1'
54 | }
55 | }
56 |
57 | dependencies {
58 | implementation Libs.Kotlin.stdlib
59 | implementation Libs.AndroidX.coreKtx
60 | implementation Libs.AndroidX.navigation
61 | implementation Libs.AndroidX.Activity.activityCompose
62 |
63 | implementation Libs.AndroidX.Compose.runtime
64 | implementation Libs.AndroidX.Compose.foundation
65 | implementation Libs.AndroidX.Compose.layout
66 | implementation Libs.AndroidX.Compose.ui
67 | implementation Libs.AndroidX.Compose.uiUtil
68 | implementation Libs.AndroidX.Compose.material
69 | implementation Libs.AndroidX.Compose.animation
70 | implementation Libs.AndroidX.Compose.iconsExtended
71 | implementation Libs.AndroidX.Compose.tooling
72 |
73 | implementation Libs.Accompanist.systemuicontroller
74 | implementation Libs.Accompanist.coil
75 | implementation Libs.Accompanist.insets
76 |
77 | //implementation Libs.LottieView.lottie
78 | //implementation Libs.LottieView.lottieCompose
79 |
80 | androidTestImplementation Libs.AndroidX.Compose.uiTestJunit
81 | debugImplementation Libs.AndroidX.Compose.uiTestManifest
82 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/play/HomeScreenTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.play
2 |
3 | import androidx.compose.ui.test.assertIsDisplayed
4 | import androidx.compose.ui.test.junit4.createAndroidComposeRule
5 | import androidx.compose.ui.test.onNodeWithText
6 | import androidx.compose.ui.test.onRoot
7 | import androidx.compose.ui.test.performClick
8 | import androidx.compose.ui.test.printToLog
9 | import com.example.play.theme.PlayTheme
10 | import com.example.play.ui.MainActivity
11 | import com.example.play.ui.PlayApp
12 | import org.junit.Rule
13 | import org.junit.Test
14 |
15 | class HomeScreenTest {
16 |
17 | @get:Rule
18 | val composeTestRule = createAndroidComposeRule()
19 | // createComposeRule() if you don't need access to the activityTestRule
20 |
21 | @Test
22 | fun testTopCharts() {
23 | // Start the app
24 | composeTestRule.setContent {
25 | PlayTheme {
26 | PlayApp {}
27 | }
28 | }
29 | composeTestRule.onNodeWithText("Top Charts").performClick()
30 | composeTestRule.onNodeWithText("Show installed apps").assertIsDisplayed()
31 | composeTestRule.onNodeWithText("Top grossing").performClick()
32 | composeTestRule.onRoot().printToLog("TAG")
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/anim/BottomNavAnim.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.anim
2 |
3 | import androidx.compose.animation.animateColorAsState
4 | import androidx.compose.animation.core.AnimationSpec
5 | import androidx.compose.animation.core.animateFloatAsState
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.State
8 | import androidx.compose.ui.graphics.Color
9 | import com.example.play.theme.PlayTheme
10 |
11 | @Composable
12 | fun getBottomNavTintState(selected: Boolean): State {
13 | return animateColorAsState(
14 | if (selected) {
15 | PlayTheme.colors.iconInteractive
16 | } else {
17 | PlayTheme.colors.iconInteractiveInactive
18 | }
19 | )
20 | }
21 |
22 | @Composable
23 | fun getProgressState(
24 | selected: Boolean,
25 | animSpec: AnimationSpec
26 | ): State {
27 | return animateFloatAsState(if (selected) 1f else 0f, animSpec)
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/anim/FilterChipAnim.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.anim
2 |
3 | import androidx.compose.animation.animateColorAsState
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.State
6 | import androidx.compose.ui.graphics.Color
7 | import com.example.play.theme.PlayTheme
8 |
9 | @Composable
10 | fun getFilterBgColorState(isSelected: Boolean): State {
11 | return animateColorAsState(
12 | if (isSelected) PlayTheme.colors.accent.copy(alpha = 0.1f)
13 | else PlayTheme.colors.uiBackground
14 | )
15 | }
16 |
17 | @Composable
18 | fun getFilterTextColorState(isSelected: Boolean): State {
19 | return animateColorAsState(
20 | if (isSelected) PlayTheme.colors.accentDark
21 | else PlayTheme.colors.textSecondary
22 | )
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/anim/HeaderAnim.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.anim
2 |
3 | import androidx.compose.animation.core.animateDpAsState
4 | import androidx.compose.animation.core.tween
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.State
7 | import androidx.compose.ui.unit.Dp
8 | import androidx.compose.ui.unit.dp
9 |
10 | @Composable
11 | fun getAppIconSizeAnimState(showProgress: Boolean): State {
12 | return animateDpAsState(
13 | targetValue = if (showProgress) 80.dp else 100.dp,
14 | animationSpec = tween(
15 | durationMillis = 500,
16 | delayMillis = 500
17 | )
18 | )
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/anim/InstallButtonAnim.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.anim
2 |
3 | import androidx.compose.animation.animateColorAsState
4 | import androidx.compose.animation.core.FastOutLinearInEasing
5 | import androidx.compose.animation.core.LinearEasing
6 | import androidx.compose.animation.core.RepeatMode.Reverse
7 | import androidx.compose.animation.core.animateDpAsState
8 | import androidx.compose.animation.core.animateFloat
9 | import androidx.compose.animation.core.animateIntAsState
10 | import androidx.compose.animation.core.infiniteRepeatable
11 | import androidx.compose.animation.core.rememberInfiniteTransition
12 | import androidx.compose.animation.core.snap
13 | import androidx.compose.animation.core.tween
14 | import androidx.compose.animation.core.updateTransition
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.runtime.State
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.unit.Dp
19 | import androidx.compose.ui.unit.dp
20 | import com.example.play.anim.ButtonState.IDLE
21 | import com.example.play.anim.ButtonState.PRESSED
22 |
23 | enum class ButtonState {
24 | IDLE,
25 | PRESSED
26 | }
27 |
28 | @Composable
29 | fun getInstallButtonOpacityState(
30 | isPressed: Boolean
31 | ): State {
32 | val currentState = if (isPressed) PRESSED else IDLE
33 | val transition = updateTransition(targetState = currentState, label = "installButtonOpacityState")
34 | return transition.animateFloat(
35 | transitionSpec = {
36 | when {
37 | IDLE isTransitioningTo PRESSED -> tween(durationMillis = 1500)
38 | PRESSED isTransitioningTo IDLE -> {
39 | tween(durationMillis = 2000)
40 | }
41 | else -> snap()
42 | }
43 | }, label = "installButtonOpacityState"
44 | ) { installButtonOpacityState ->
45 | when (installButtonOpacityState) {
46 | IDLE -> 0f
47 | PRESSED -> 0.5f
48 | }
49 | }
50 | }
51 |
52 | @Composable
53 | fun getButtonIconSizeState(): State {
54 | val transition = rememberInfiniteTransition()
55 | return transition.animateFloat(
56 | initialValue = 0f,
57 | targetValue = 24f,
58 | animationSpec = infiniteRepeatable(
59 | tween(500), Reverse
60 | )
61 | )
62 | }
63 |
64 | @Composable
65 | fun getOpenButtonWidthState(isPressed: Boolean): State {
66 | return animateDpAsState(
67 | targetValue = if (isPressed) 150.dp else 0.dp,
68 | animationSpec = tween(
69 | durationMillis = 800,
70 | delayMillis = if (isPressed) 0 else 1500
71 | )
72 | )
73 | }
74 |
75 | @Composable
76 | fun getInstallButtonWidthState(isPressed: Boolean): State {
77 | return animateDpAsState(
78 | targetValue = if (isPressed) 150.dp else 340.dp,
79 | animationSpec = tween(
80 | durationMillis = 1500,
81 | delayMillis = if (isPressed) 800 else 0
82 | )
83 | )
84 | }
85 |
86 | @Composable
87 | fun getButtonColorState(isPressed: Boolean): State {
88 | return animateColorAsState(
89 | targetValue = if (isPressed) Color(0xff01875f) else Color.White,
90 | animationSpec = tween(
91 | durationMillis = 500
92 | )
93 | )
94 | }
95 |
96 | @Composable
97 | fun getInstallBtnBorderColorState(isPressed: Boolean): State {
98 | return animateColorAsState(
99 | targetValue = if (isPressed) Color.White else Color(0xff01875f)
100 | )
101 | }
102 |
103 | @Composable
104 | fun getInstallBtnBorderWidthState(isPressed: Boolean): State {
105 | return animateDpAsState(
106 | targetValue = if (isPressed) 0.dp else 1.dp,
107 | )
108 | }
109 |
110 | @Composable
111 | fun getInstallBtnBgColorState(isPressed: Boolean): State {
112 | return animateColorAsState(
113 | targetValue = if (isPressed) Color.White else Color(0xff01875f),
114 | animationSpec = tween(
115 | durationMillis = 3000
116 | )
117 | )
118 | }
119 |
120 | @Composable
121 | fun getInstallBtnCornerState(isPressed: Boolean): State {
122 | return animateIntAsState(
123 | targetValue = if (isPressed) 50 else 10,
124 | animationSpec = tween(
125 | durationMillis = 3000,
126 | easing = FastOutLinearInEasing,
127 | )
128 | )
129 | }
130 |
131 | @Composable
132 | fun getOpenButtonGapWidthState(isPressed: Boolean): State {
133 | return animateDpAsState(
134 | targetValue = if (isPressed) 8.dp else 0.dp,
135 | animationSpec = tween(
136 | durationMillis = 800,
137 | delayMillis = if (isPressed) 800 else 1500,
138 | easing = LinearEasing
139 | )
140 | )
141 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/anim/RatingBarAnim.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.anim
2 |
3 | import androidx.compose.animation.core.FastOutSlowInEasing
4 | import androidx.compose.animation.core.animateFloat
5 | import androidx.compose.animation.core.snap
6 | import androidx.compose.animation.core.tween
7 | import androidx.compose.animation.core.updateTransition
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.State
10 |
11 | enum class AppRatingBarState {
12 | START,
13 | END
14 | }
15 |
16 | @Composable
17 | fun getAppRatingBarState(
18 | progress: Float,
19 | durationMillis: Int = 3000,
20 | showProgress: Boolean
21 | ): State {
22 | val currentState = if (showProgress) AppRatingBarState.END else AppRatingBarState.START
23 | val transition = updateTransition(targetState = currentState, label = "appRatingBarState")
24 | return transition.animateFloat(
25 | transitionSpec = {
26 | when {
27 | AppRatingBarState.START isTransitioningTo AppRatingBarState.END ->
28 | tween(easing = FastOutSlowInEasing, durationMillis = durationMillis)
29 | AppRatingBarState.END isTransitioningTo AppRatingBarState.START -> {
30 | tween(easing = FastOutSlowInEasing, durationMillis = durationMillis)
31 | }
32 | else -> snap()
33 | }
34 | }, label = "appRatingBarState"
35 | ) { appRatingBarState ->
36 | when (appRatingBarState) {
37 | AppRatingBarState.START -> 0f
38 | AppRatingBarState.END -> progress
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/data/Models.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.data
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.runtime.Stable
5 | import androidx.compose.runtime.mutableStateOf
6 |
7 | @Immutable
8 | data class App(
9 | val id: Long,
10 | val name: String,
11 | val imageUrl: String,
12 | val featureImageUrl: String = "",
13 | val size: String = "7 MB",
14 | val category: String = "Utility",
15 | val filterCategory: String = "Top free",
16 | val type: String = "Action",
17 | val ratings: String = "4.1",
18 | val org: String = "XCompany",
19 | val info: String = "Contains Ads",
20 | val reviews: List = listOf(),
21 | val tags: Set = emptySet()
22 | )
23 |
24 | @Immutable
25 | data class Movie(
26 | val id: Long,
27 | val name: String,
28 | val imageUrl: String = "",
29 | val category: String = "Drama",
30 | val filterCategory: String = "For You",
31 | val ratings: String = "3.5",
32 | val price: String = "₹50.00",
33 | )
34 |
35 | @Immutable
36 | data class Review(
37 | val id: Long = 1,
38 | val userName: String = "John Barretto",
39 | val userAvatarUrl: String = "",
40 | val reviewDesc: String = "Amazing App!",
41 | val ratings: Double = 4.5,
42 | val date: String = "9/25/20",
43 | val appId: Long = 1
44 | )
45 |
46 | @Stable
47 | class Filter(
48 | id: Int,
49 | val name: String,
50 | enabled: Boolean = false
51 | ) {
52 | val enabled = mutableStateOf(enabled)
53 | val id = mutableStateOf(id)
54 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val White = Color(0xffffffff)
6 | val Green = Color(0xff00a173)
7 |
8 | val VeryLightGrey = Color(0xffdadce0)
9 | val LightGrey = Color(0xffe8eaed)
10 | val Grey = Color(0xff5f6368)
11 | val TextPrimary = Color(0xff202124)
12 | val TextSecondaryDark = Color(0xff212121)
13 | val TextSecondary = Color(0xff5f6368)
14 | val DeepGreen = Color(0xff01875f)
15 | val DarkGreen = Color(0xff056449)
16 | val GreyBg = Color(0xff202125)
17 |
18 | val Shadow11 = Color(0xff001787)
19 | val Shadow10 = Color(0xff00119e)
20 | val Shadow9 = Color(0xff0009b3)
21 | val Shadow8 = Color(0xff0200c7)
22 | val Shadow7 = Color(0xff0e00d7)
23 | val Shadow6 = Color(0xff2a13e4)
24 | val Shadow5 = Color(0xff4b30ed)
25 | val Shadow4 = Color(0xff7057f5)
26 | val Shadow3 = Color(0xff9b86fa)
27 | val Shadow2 = Color(0xffc8bbfd)
28 | val Shadow1 = Color(0xffded6fe)
29 | val Shadow0 = Color(0xfff4f2ff)
30 |
31 | val Ocean11 = Color(0xff005687)
32 | val Ocean10 = Color(0xff006d9e)
33 | val Ocean9 = Color(0xff0087b3)
34 | val Ocean8 = Color(0xff00a1c7)
35 | val Ocean7 = Color(0xff00b9d7)
36 | val Ocean6 = Color(0xff13d0e4)
37 | val Ocean5 = Color(0xff30e2ed)
38 | val Ocean4 = Color(0xff57eff5)
39 | val Ocean3 = Color(0xff86f7fa)
40 | val Ocean2 = Color(0xffbbfdfd)
41 | val Ocean1 = Color(0xffd6fefe)
42 | val Ocean0 = Color(0xfff2ffff)
43 |
44 | val Lavender11 = Color(0xff170085)
45 | val Lavender10 = Color(0xff23009e)
46 | val Lavender9 = Color(0xff3300b3)
47 | val Lavender8 = Color(0xff4400c7)
48 | val Lavender7 = Color(0xff5500d7)
49 | val Lavender6 = Color(0xff6f13e4)
50 | val Lavender5 = Color(0xff8a30ed)
51 | val Lavender4 = Color(0xffa557f5)
52 | val Lavender3 = Color(0xffc186fa)
53 | val Lavender2 = Color(0xffdebbfd)
54 | val Lavender1 = Color(0xffebd6fe)
55 | val Lavender0 = Color(0xfff9f2ff)
56 |
57 | val Rose11 = Color(0xff7f0054)
58 | val Rose10 = Color(0xff97005c)
59 | val Rose9 = Color(0xffaf0060)
60 | val Rose8 = Color(0xffc30060)
61 | val Rose7 = Color(0xffd4005d)
62 | val Rose6 = Color(0xffe21365)
63 | val Rose5 = Color(0xffec3074)
64 | val Rose4 = Color(0xfff4568b)
65 | val Rose3 = Color(0xfff985aa)
66 | val Rose2 = Color(0xfffdbbcf)
67 | val Rose1 = Color(0xfffed6e2)
68 | val Rose0 = Color(0xfffff2f6)
69 |
70 | val Neutral8 = Color(0xff121212)
71 | val Neutral7 = Color(0xdef000000)
72 | val Neutral6 = Color(0x99000000)
73 | val Neutral5 = Color(0x61000000)
74 | val Neutral4 = Color(0x1f000000)
75 | val Neutral3 = Color(0x1fffffff)
76 | val Neutral2 = Color(0x61ffffff)
77 | val Neutral1 = Color(0xbdffffff)
78 | val Neutral0 = Color(0xffffffff)
79 |
80 | val FunctionalRed = Color(0xffd00036)
81 | val FunctionalRedDark = Color(0xffea6d7e)
82 | val FunctionalGreen = Color(0xff52c41a)
83 | val FunctionalGrey = Color(0xfff6f6f6)
84 | val FunctionalDarkGrey = Color(0xff2e2e2e)
85 |
86 | const val AlphaNearOpaque = 0.95f
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val Shapes = Shapes(
8 | small = RoundedCornerShape(percent = 50),
9 | medium = RoundedCornerShape(20.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material.Colors
5 | import androidx.compose.material.MaterialTheme
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.CompositionLocalProvider
8 | import androidx.compose.runtime.SideEffect
9 | import androidx.compose.runtime.Stable
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.runtime.setValue
14 | import androidx.compose.runtime.staticCompositionLocalOf
15 | import androidx.compose.ui.graphics.Color
16 | import com.google.accompanist.systemuicontroller.rememberSystemUiController
17 |
18 | private val LightColorPalette = PlayColorPalette(
19 | brand = White,
20 | accent = DeepGreen,
21 | accentDark = DarkGreen,
22 | iconTint = Grey,
23 | uiBackground = Neutral0,
24 | uiBorder = VeryLightGrey,
25 | uiFloated = FunctionalGrey,
26 | textPrimary = TextPrimary,
27 | textSecondary = TextSecondary,
28 | textSecondaryDark = TextSecondaryDark,
29 | textHelp = Neutral6,
30 | textInteractive = Neutral0,
31 | textLink = Ocean11,
32 | iconSecondary = Neutral7,
33 | iconInteractive = Green,
34 | iconInteractiveInactive = Grey,
35 | error = FunctionalRed,
36 | gradient6_1 = listOf(Shadow4, Ocean3, Shadow2, Ocean3, Shadow4),
37 | gradient6_2 = listOf(Rose4, Lavender3, Rose2, Lavender3, Rose4),
38 | gradient3_1 = listOf(Shadow2, Ocean3, Shadow4),
39 | gradient3_2 = listOf(Rose2, Lavender3, Rose4),
40 | gradient2_1 = listOf(Shadow4, Shadow11),
41 | gradient2_2 = listOf(Ocean3, Shadow3),
42 | progressIndicatorBg = LightGrey,
43 | switchColor = Green,
44 | isDark = false
45 | )
46 |
47 | private val DarkColorPalette = PlayColorPalette(
48 | brand = Shadow1,
49 | accent = DeepGreen,
50 | accentDark = DarkGreen,
51 | iconTint = Shadow1,
52 | uiBackground = GreyBg,
53 | uiBorder = Neutral3,
54 | uiFloated = FunctionalDarkGrey,
55 | textPrimary = Shadow1,
56 | textSecondary = Neutral0,
57 | textHelp = Neutral1,
58 | textInteractive = Neutral7,
59 | textLink = Ocean2,
60 | iconPrimary = Neutral3,
61 | iconSecondary = Neutral0,
62 | textSecondaryDark = Neutral0,
63 | iconInteractive = White,
64 | iconInteractiveInactive = Neutral2,
65 | error = FunctionalRedDark,
66 | gradient6_1 = listOf(Shadow5, Ocean7, Shadow9, Ocean7, Shadow5),
67 | gradient6_2 = listOf(Rose11, Lavender7, Rose8, Lavender7, Rose11),
68 | gradient3_1 = listOf(Shadow9, Ocean7, Shadow5),
69 | gradient3_2 = listOf(Rose8, Lavender7, Rose11),
70 | gradient2_1 = listOf(Ocean3, Shadow3),
71 | gradient2_2 = listOf(Ocean7, Shadow7),
72 | progressIndicatorBg = LightGrey,
73 | switchColor = Green,
74 | isDark = true
75 | )
76 |
77 | @Composable
78 | fun PlayTheme(
79 | darkTheme: Boolean = isSystemInDarkTheme(),
80 | content: @Composable () -> Unit
81 | ) {
82 | val colors = if (darkTheme) DarkColorPalette else LightColorPalette
83 |
84 | val sysUiController = rememberSystemUiController()
85 | SideEffect {
86 | sysUiController.setSystemBarsColor(
87 | color = colors.uiBackground.copy(alpha = AlphaNearOpaque)
88 | )
89 | }
90 |
91 | ProvidePlayColors(colors) {
92 | MaterialTheme(
93 | colors = debugColors(darkTheme),
94 | typography = Typography,
95 | shapes = Shapes,
96 | content = content
97 | )
98 | }
99 | }
100 |
101 | object PlayTheme {
102 | val colors: PlayColorPalette
103 | @Composable
104 | get() = LocalPlayColor.current
105 | }
106 |
107 | /**
108 | * Play custom Color Palette
109 | */
110 | @Stable
111 | class PlayColorPalette(
112 | gradient6_1: List,
113 | gradient6_2: List,
114 | gradient3_1: List,
115 | gradient3_2: List,
116 | gradient2_1: List,
117 | gradient2_2: List,
118 | brand: Color,
119 | accent: Color,
120 | accentDark: Color,
121 | iconTint: Color,
122 | uiBackground: Color,
123 | uiBorder: Color,
124 | uiFloated: Color,
125 | interactivePrimary: List = gradient2_1,
126 | interactiveSecondary: List = gradient2_2,
127 | interactiveMask: List = gradient6_1,
128 | textPrimary: Color = brand,
129 | textSecondaryDark: Color,
130 | textSecondary: Color,
131 | textHelp: Color,
132 | textInteractive: Color,
133 | textLink: Color,
134 | iconPrimary: Color = brand,
135 | iconSecondary: Color,
136 | iconInteractive: Color,
137 | iconInteractiveInactive: Color,
138 | error: Color,
139 | notificationBadge: Color = error,
140 | progressIndicatorBg: Color,
141 | switchColor: Color,
142 | isDark: Boolean
143 | ) {
144 | var gradient6_1 by mutableStateOf(gradient6_1)
145 | private set
146 | var gradient6_2 by mutableStateOf(gradient6_2)
147 | private set
148 | var gradient3_1 by mutableStateOf(gradient3_1)
149 | private set
150 | var gradient3_2 by mutableStateOf(gradient3_2)
151 | private set
152 | var gradient2_1 by mutableStateOf(gradient2_1)
153 | private set
154 | var gradient2_2 by mutableStateOf(gradient2_2)
155 | private set
156 | var brand by mutableStateOf(brand)
157 | private set
158 | var accent by mutableStateOf(accent)
159 | private set
160 | var accentDark by mutableStateOf(accentDark)
161 | private set
162 | var iconTint by mutableStateOf(iconTint)
163 | private set
164 | var uiBackground by mutableStateOf(uiBackground)
165 | private set
166 | var uiBorder by mutableStateOf(uiBorder)
167 | private set
168 | var uiFloated by mutableStateOf(uiFloated)
169 | private set
170 | var interactivePrimary by mutableStateOf(interactivePrimary)
171 | private set
172 | var interactiveSecondary by mutableStateOf(interactiveSecondary)
173 | private set
174 | var interactiveMask by mutableStateOf(interactiveMask)
175 | private set
176 | var textPrimary by mutableStateOf(textPrimary)
177 | private set
178 | var textSecondary by mutableStateOf(textSecondary)
179 | private set
180 | var textSecondaryDark by mutableStateOf(textSecondaryDark)
181 | private set
182 | var textHelp by mutableStateOf(textHelp)
183 | private set
184 | var textInteractive by mutableStateOf(textInteractive)
185 | private set
186 | var textLink by mutableStateOf(textLink)
187 | private set
188 | var iconPrimary by mutableStateOf(iconPrimary)
189 | private set
190 | var iconSecondary by mutableStateOf(iconSecondary)
191 | private set
192 | var iconInteractive by mutableStateOf(iconInteractive)
193 | private set
194 | var iconInteractiveInactive by mutableStateOf(iconInteractiveInactive)
195 | private set
196 | var error by mutableStateOf(error)
197 | private set
198 | var notificationBadge by mutableStateOf(notificationBadge)
199 | private set
200 | var progressIndicatorBg by mutableStateOf(progressIndicatorBg)
201 | private set
202 | var switchColor by mutableStateOf(switchColor)
203 | private set
204 | var isDark by mutableStateOf(isDark)
205 | private set
206 |
207 | fun update(other: PlayColorPalette) {
208 | gradient6_1 = other.gradient6_1
209 | gradient6_2 = other.gradient6_2
210 | gradient3_1 = other.gradient3_1
211 | gradient3_2 = other.gradient3_2
212 | gradient2_1 = other.gradient2_1
213 | gradient2_2 = other.gradient2_2
214 | brand = other.brand
215 | uiBackground = other.uiBackground
216 | uiBorder = other.uiBorder
217 | uiFloated = other.uiFloated
218 | interactivePrimary = other.interactivePrimary
219 | interactiveSecondary = other.interactiveSecondary
220 | interactiveMask = other.interactiveMask
221 | textPrimary = other.textPrimary
222 | textSecondary = other.textSecondary
223 | textHelp = other.textHelp
224 | textInteractive = other.textInteractive
225 | textLink = other.textLink
226 | iconPrimary = other.iconPrimary
227 | iconSecondary = other.iconSecondary
228 | iconInteractive = other.iconInteractive
229 | iconInteractiveInactive = other.iconInteractiveInactive
230 | error = other.error
231 | notificationBadge = other.notificationBadge
232 | switchColor = other.switchColor
233 | isDark = other.isDark
234 | }
235 | }
236 |
237 | @Composable
238 | fun ProvidePlayColors(
239 | colors: PlayColorPalette,
240 | content: @Composable () -> Unit
241 | ) {
242 | val colorPalette = remember { colors }
243 | colorPalette.update(colors)
244 | CompositionLocalProvider(LocalPlayColor provides colorPalette, content = content)
245 | }
246 |
247 | private val LocalPlayColor = staticCompositionLocalOf {
248 | error("No PlayColorPalette provided")
249 | }
250 |
251 | /**
252 | * A Material [Colors] implementation which sets all colors to [debugColor] to discourage usage of
253 | * [MaterialTheme.colors] in preference to [PlayTheme.colors].
254 | */
255 | fun debugColors(
256 | darkTheme: Boolean,
257 | debugColor: Color = Color.Red
258 | ) = Colors(
259 | primary = debugColor,
260 | primaryVariant = debugColor,
261 | secondary = debugColor,
262 | secondaryVariant = debugColor,
263 | background = debugColor,
264 | surface = debugColor,
265 | error = debugColor,
266 | onPrimary = debugColor,
267 | onSecondary = debugColor,
268 | onBackground = debugColor,
269 | onSurface = debugColor,
270 | onError = debugColor,
271 | isLight = !darkTheme
272 | )
273 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.Font
6 | import androidx.compose.ui.text.font.FontFamily
7 | import androidx.compose.ui.text.font.FontWeight
8 | import androidx.compose.ui.unit.sp
9 | import com.example.play.R.font
10 |
11 | private val ProductSans = FontFamily(
12 | Font(font.product_sans_regular, FontWeight.Normal),
13 | Font(font.product_sans_bold, FontWeight.Bold)
14 | )
15 |
16 | val Typography = Typography(
17 | h1 = TextStyle(
18 | fontFamily = ProductSans,
19 | fontSize = 96.sp,
20 | fontWeight = FontWeight.Normal,
21 | lineHeight = 117.sp,
22 | letterSpacing = (-1.5).sp
23 | ),
24 | h2 = TextStyle(
25 | fontFamily = ProductSans,
26 | fontSize = 60.sp,
27 | fontWeight = FontWeight.Normal,
28 | lineHeight = 73.sp,
29 | letterSpacing = (-0.5).sp
30 | ),
31 | h3 = TextStyle(
32 | fontFamily = ProductSans,
33 | fontSize = 48.sp,
34 | fontWeight = FontWeight.Normal,
35 | lineHeight = 59.sp
36 | ),
37 | h4 = TextStyle(
38 | fontFamily = ProductSans,
39 | fontSize = 30.sp,
40 | fontWeight = FontWeight.Normal,
41 | lineHeight = 37.sp
42 | ),
43 | h5 = TextStyle(
44 | fontFamily = ProductSans,
45 | fontSize = 24.sp,
46 | fontWeight = FontWeight.Normal,
47 | lineHeight = 29.sp
48 | ),
49 | h6 = TextStyle(
50 | fontFamily = ProductSans,
51 | fontSize = 20.sp,
52 | fontWeight = FontWeight.Normal,
53 | lineHeight = 24.sp
54 | ),
55 | subtitle1 = TextStyle(
56 | fontFamily = ProductSans,
57 | fontSize = 16.sp,
58 | fontWeight = FontWeight.Normal,
59 | lineHeight = 24.sp,
60 | letterSpacing = 0.15.sp
61 | ),
62 | subtitle2 = TextStyle(
63 | fontFamily = ProductSans,
64 | fontSize = 14.sp,
65 | fontWeight = FontWeight.Normal,
66 | lineHeight = 24.sp,
67 | letterSpacing = 0.1.sp
68 | ),
69 | body1 = TextStyle(
70 | fontFamily = ProductSans,
71 | fontSize = 16.sp,
72 | fontWeight = FontWeight.Normal,
73 | lineHeight = 28.sp,
74 | letterSpacing = 0.15.sp
75 | ),
76 | body2 = TextStyle(
77 | fontFamily = ProductSans,
78 | fontSize = 14.sp,
79 | fontWeight = FontWeight.Normal,
80 | lineHeight = 20.sp,
81 | letterSpacing = 0.25.sp
82 | ),
83 | button = TextStyle(
84 | fontFamily = ProductSans,
85 | fontSize = 14.sp,
86 | fontWeight = FontWeight.Normal,
87 | lineHeight = 16.sp,
88 | letterSpacing = 1.25.sp
89 | ),
90 | caption = TextStyle(
91 | fontFamily = ProductSans,
92 | fontSize = 12.sp,
93 | fontWeight = FontWeight.Normal,
94 | lineHeight = 16.sp,
95 | letterSpacing = 0.4.sp
96 | ),
97 | overline = TextStyle(
98 | fontFamily = ProductSans,
99 | fontSize = 12.sp,
100 | fontWeight = FontWeight.Normal,
101 | lineHeight = 16.sp,
102 | letterSpacing = 1.sp
103 | )
104 | )
105 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/BottomNav.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui
2 |
3 | import androidx.annotation.FloatRange
4 | import androidx.compose.animation.core.Animatable
5 | import androidx.compose.animation.core.AnimationSpec
6 | import androidx.compose.animation.core.SpringSpec
7 | import androidx.compose.foundation.border
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.BoxScope
10 | import androidx.compose.foundation.layout.Column
11 | import androidx.compose.foundation.layout.Spacer
12 | import androidx.compose.foundation.layout.fillMaxSize
13 | import androidx.compose.foundation.layout.height
14 | import androidx.compose.foundation.layout.padding
15 | import androidx.compose.foundation.selection.selectable
16 | import androidx.compose.foundation.shape.RoundedCornerShape
17 | import androidx.compose.material.Icon
18 | import androidx.compose.material.MaterialTheme
19 | import androidx.compose.material.Text
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.runtime.LaunchedEffect
22 | import androidx.compose.runtime.getValue
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.ui.Alignment
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.draw.clip
27 | import androidx.compose.ui.graphics.Color
28 | import androidx.compose.ui.graphics.Shape
29 | import androidx.compose.ui.graphics.TransformOrigin
30 | import androidx.compose.ui.graphics.graphicsLayer
31 | import androidx.compose.ui.layout.Layout
32 | import androidx.compose.ui.layout.MeasureResult
33 | import androidx.compose.ui.layout.MeasureScope
34 | import androidx.compose.ui.layout.Placeable
35 | import androidx.compose.ui.layout.layoutId
36 | import androidx.compose.ui.res.stringResource
37 | import androidx.compose.ui.unit.Dp
38 | import androidx.compose.ui.unit.dp
39 | import androidx.compose.ui.util.lerp
40 | import androidx.navigation.NavHostController
41 | import androidx.navigation.compose.currentBackStackEntryAsState
42 | import com.example.play.anim.getBottomNavTintState
43 | import com.example.play.anim.getProgressState
44 | import com.example.play.theme.PlayTheme
45 | import com.example.play.ui.components.PlaySurface
46 | import com.google.accompanist.insets.navigationBarsPadding
47 | import java.util.Locale
48 |
49 | private val TextIconSpacing = 5.dp
50 | private val BottomNavHeight = 56.dp
51 | private val BottomNavLabelTransformOrigin = TransformOrigin(0f, 0.5f)
52 | private val BottomNavIndicatorShape = RoundedCornerShape(percent = 50)
53 | private val BottomNavigationItemPadding = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
54 |
55 | // Bottom Navigation
56 | @Composable
57 | fun PlayBottomNav(
58 | navController: NavHostController,
59 | tabs: Array,
60 | onTabSelected: (String, String) -> Unit,
61 | color: Color = PlayTheme.colors.iconPrimary,
62 | contentColor: Color = PlayTheme.colors.iconInteractive
63 | ) {
64 | val navBackStackEntry by navController.currentBackStackEntryAsState()
65 | val currentRoute = navBackStackEntry?.destination?.route ?: BottomNavTabs.Games.route
66 | val routes = remember { BottomNavTabs.values().map { it.route } }
67 |
68 | PlaySurface(
69 | color = color,
70 | contentColor = contentColor
71 | ) {
72 | val springSpec = remember { getAnimSpec() }
73 | if (currentRoute in routes) {
74 | Column {
75 | PlayBottomNavLayout(
76 | selectedIndex = routes.indexOf(currentRoute),
77 | itemCount = tabs.size,
78 | indicator = { PLayBottomNavIndicator() },
79 | animSpec = springSpec,
80 | modifier = Modifier.navigationBarsPadding(left = false, right = false)
81 | ) {
82 | tabs.forEach { tab ->
83 | val selected = currentRoute == tab.route
84 | val tint by getBottomNavTintState(selected = selected)
85 |
86 | PlayBottomNavigationItem(
87 | icon = {
88 | Icon(
89 | imageVector = tab.icon,
90 | tint = tint,
91 | contentDescription = null
92 | )
93 | },
94 | text = {
95 | Text(
96 | text = stringResource(tab.title).toUpperCase(Locale.ROOT),
97 | color = tint,
98 | style = MaterialTheme.typography.button,
99 | maxLines = 1
100 | )
101 | },
102 | selected = selected,
103 | onSelected = { onTabSelected(tab.route, currentRoute) },
104 | animSpec = springSpec,
105 | modifier = BottomNavigationItemPadding
106 | .clip(BottomNavIndicatorShape)
107 | )
108 | }
109 | }
110 | }
111 | }
112 | }
113 | }
114 |
115 | // Bottom Navigation Item
116 | @Composable
117 | fun PlayBottomNavigationItem(
118 | icon: @Composable BoxScope.() -> Unit,
119 | text: @Composable BoxScope.() -> Unit,
120 | selected: Boolean,
121 | onSelected: () -> Unit,
122 | animSpec: AnimationSpec,
123 | modifier: Modifier = Modifier
124 | ) {
125 | Box(
126 | modifier = modifier.selectable(selected = selected, onClick = onSelected),
127 | contentAlignment = Alignment.Center
128 | ) {
129 | // Animate the icon/text positions within the item based on selection
130 | val animationProgress by getProgressState(selected = selected, animSpec = animSpec)
131 | PlayBottomNavItemLayout(
132 | icon = icon,
133 | text = text,
134 | animationProgress = animationProgress
135 | )
136 | }
137 | }
138 |
139 | // Bottom Navigation Item Layout
140 | @Composable
141 | private fun PlayBottomNavItemLayout(
142 | icon: @Composable BoxScope.() -> Unit,
143 | text: @Composable BoxScope.() -> Unit,
144 | @FloatRange(from = 0.0, to = 1.0) animationProgress: Float
145 | ) {
146 | Layout(
147 | content = {
148 | Box(Modifier.layoutId("icon"), content = icon)
149 | val scale = lerp(start = 0.2f, stop = 1f, fraction = animationProgress)
150 | Box(
151 | modifier = Modifier
152 | .padding(start = TextIconSpacing)
153 | .layoutId("text")
154 | .graphicsLayer {
155 | alpha = animationProgress
156 | scaleX = scale
157 | scaleY = scale
158 | transformOrigin = BottomNavLabelTransformOrigin
159 | },
160 | content = text
161 | )
162 | }
163 | ) { measurable, constraints ->
164 | val iconPlaceable = measurable.first { it.layoutId == "icon" }
165 | .measure(constraints)
166 | val textPlaceable = measurable.first { it.layoutId == "text" }
167 | .measure(constraints)
168 |
169 | placeTextAndIcon(
170 | textPlaceable,
171 | iconPlaceable,
172 | constraints.maxWidth,
173 | constraints.maxHeight,
174 | animationProgress
175 | )
176 | }
177 | }
178 |
179 | // Place text and icon using Placeable
180 | private fun MeasureScope.placeTextAndIcon(
181 | textPlaceable: Placeable,
182 | iconPlaceable: Placeable,
183 | width: Int,
184 | height: Int,
185 | @FloatRange(from = 0.0, to = 1.0) animationProgress: Float
186 | ): MeasureResult {
187 | val iconY = (height - iconPlaceable.height) / 2
188 | val textY = (height - textPlaceable.height) / 2
189 |
190 | val textWidth = textPlaceable.width * animationProgress
191 | val iconX = (width - textWidth - iconPlaceable.width) / 2
192 | val textX = iconX + iconPlaceable.width
193 |
194 | return layout(width, height) {
195 | iconPlaceable.place(iconX.toInt(), iconY)
196 | if (animationProgress != 0f) {
197 | textPlaceable.place(textX.toInt(), textY)
198 | }
199 | }
200 | }
201 |
202 | // Bottom Navigation Layout
203 | @Composable
204 | private fun PlayBottomNavLayout(
205 | selectedIndex: Int,
206 | itemCount: Int,
207 | animSpec: AnimationSpec,
208 | indicator: @Composable BoxScope.() -> Unit,
209 | modifier: Modifier = Modifier,
210 | content: @Composable () -> Unit
211 | ) {
212 | // Track how "selected" each item is [0, 1]
213 | val selectionFractions = remember(itemCount) {
214 | List(itemCount) { i ->
215 | Animatable(if (i == selectedIndex) 1f else 0f)
216 | }
217 | }
218 |
219 | selectionFractions.forEachIndexed { index, selectionFraction ->
220 | val target = if (index == selectedIndex) 1f else 0f
221 | LaunchedEffect(target, animSpec) {
222 | selectionFraction.animateTo(target, animSpec)
223 | }
224 | }
225 |
226 | // Animate the position of the indicator
227 | val indicatorIndex = remember { Animatable(0f) }
228 | val targetIndicatorIndex = selectedIndex.toFloat()
229 | LaunchedEffect(targetIndicatorIndex) {
230 | indicatorIndex.animateTo(targetIndicatorIndex, animSpec)
231 | }
232 |
233 | Layout(
234 | modifier = modifier.height(BottomNavHeight),
235 | content = {
236 | content()
237 | Box(Modifier.layoutId("indicator"), content = indicator)
238 | }
239 | ) { measurable, constraints ->
240 | check(itemCount == (measurable.size - 1)) // account for indicator
241 |
242 | // Divide the width into n + 1 slots and give the selected item 2 slots
243 | val unselectedWidth = constraints.maxWidth / (itemCount + 1)
244 | val selectedWidth = constraints.maxWidth - (itemCount - 1) * unselectedWidth
245 | val indicatorMeasurable = measurable.first { it.layoutId == "indicator" }
246 |
247 | val itemPlaceable = measurable
248 | .filterNot { it == indicatorMeasurable }
249 | .mapIndexed { index, measurables ->
250 | // Animate item's width based upon the selection amount
251 | val width = lerp(unselectedWidth, selectedWidth, selectionFractions[index].value)
252 | measurables.measure(
253 | constraints.copy(
254 | minWidth = width,
255 | maxWidth = width
256 | )
257 | )
258 | }
259 |
260 | val indicatorPlaceable = indicatorMeasurable.measure(
261 | constraints.copy(
262 | minWidth = selectedWidth,
263 | maxWidth = selectedWidth
264 | )
265 | )
266 |
267 | layout(
268 | width = constraints.maxWidth,
269 | height = itemPlaceable.maxByOrNull { it.height }?.height ?: 0
270 | ) {
271 | val indicatorLeft = indicatorIndex.value * unselectedWidth
272 | indicatorPlaceable.place(x = indicatorLeft.toInt(), y = 0)
273 | var x = 0
274 | itemPlaceable.forEach { placeable ->
275 | placeable.place(x = x, y = 0)
276 | x += placeable.width
277 | }
278 | }
279 | }
280 | }
281 |
282 | // Bottom Navigation Indicator
283 | @Composable
284 | private fun PLayBottomNavIndicator(
285 | strokeWidth: Dp = 1.dp,
286 | color: Color = PlayTheme.colors.iconInteractive,
287 | shape: Shape = BottomNavIndicatorShape
288 | ) {
289 | Spacer(
290 | modifier = Modifier
291 | .fillMaxSize()
292 | .then(BottomNavigationItemPadding)
293 | .border(strokeWidth, color, shape)
294 | )
295 | }
296 |
297 | private fun getAnimSpec(): SpringSpec {
298 | return SpringSpec(
299 | // Determined experimentally
300 | stiffness = 200f,
301 | dampingRatio = 0.9f
302 | )
303 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.core.view.WindowCompat
7 |
8 | class MainActivity : ComponentActivity() {
9 | override fun onCreate(savedInstanceState: Bundle?) {
10 | super.onCreate(savedInstanceState)
11 |
12 | // This app draws behind the system bars, so we want to handle fitting system windows
13 | WindowCompat.setDecorFitsSystemWindows(window, true)
14 |
15 | setContent {
16 | PlayApp { finish() }
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/NavGraph.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.play.ui
18 |
19 | import androidx.activity.compose.BackHandler
20 | import androidx.annotation.StringRes
21 | import androidx.compose.material.icons.Icons
22 | import androidx.compose.material.icons.outlined.Apps
23 | import androidx.compose.material.icons.outlined.Book
24 | import androidx.compose.material.icons.outlined.Games
25 | import androidx.compose.material.icons.outlined.Movie
26 | import androidx.compose.runtime.Composable
27 | import androidx.compose.ui.Modifier
28 | import androidx.compose.ui.graphics.vector.ImageVector
29 | import androidx.lifecycle.Lifecycle
30 | import androidx.navigation.NavBackStackEntry
31 | import androidx.navigation.NavGraphBuilder
32 | import androidx.navigation.NavHostController
33 | import androidx.navigation.NavType
34 | import androidx.navigation.compose.NavHost
35 | import androidx.navigation.compose.composable
36 | import androidx.navigation.compose.navArgument
37 | import androidx.navigation.compose.rememberNavController
38 | import androidx.navigation.navigation
39 | import com.example.play.R
40 | import com.example.play.ui.apps.Apps
41 | import com.example.play.ui.books.Books
42 | import com.example.play.ui.details.AppDetails
43 | import com.example.play.ui.games.Games
44 | import com.example.play.ui.movies.Movies
45 |
46 | /**
47 | * Main destinations used in the ([PlayApp]).
48 | */
49 | object MainDestinations {
50 | const val MAIN_ROUTE = "main"
51 | const val APP_DETAILS_ROUTE = "details"
52 | const val APP_ID_KEY = "appId"
53 | }
54 |
55 | /**
56 | * Bottom nav destinations used in the ([PlayApp]).
57 | */
58 | object BottomNavDestinations {
59 | const val GAMES_ROUTE = "bottom/nav/games"
60 | const val APPS_ROUTE = "bottom/nav/apps"
61 | const val MOVIES_ROUTE = "bottom/nav/movies"
62 | const val BOOKS_ROUTE = "bottom/nav/books"
63 | }
64 |
65 | @Composable
66 | fun NavGraph(
67 | modifier: Modifier = Modifier,
68 | finishActivity: () -> Unit = {},
69 | navController: NavHostController = rememberNavController(),
70 | actions: MainActions,
71 | startDestination: String = MainDestinations.MAIN_ROUTE,
72 | shouldShowAppBar: (Boolean) -> Unit
73 | ) {
74 | NavHost(
75 | navController = navController,
76 | startDestination = startDestination
77 | ) {
78 |
79 | /*
80 | * Add the PlayApp composable to the NavGraphBuilder
81 | */
82 | composable(
83 | route = MainDestinations.MAIN_ROUTE
84 | ) {
85 | PlayApp { finishActivity() }
86 | }
87 |
88 | /*
89 | * Add the AppDetails composable to the NavGraphBuilder
90 | */
91 | composable(
92 | route = "${MainDestinations.APP_DETAILS_ROUTE}/{${MainDestinations.APP_ID_KEY}}",
93 | arguments = listOf(navArgument(MainDestinations.APP_ID_KEY) { type = NavType.LongType })
94 | ) { backStackEntry: NavBackStackEntry ->
95 | // Intercept back in App Details
96 | BackHandler {
97 | shouldShowAppBar(true)
98 | navController.navigateUp()
99 | }
100 | AppDetails(
101 | backStackEntry.arguments?.getLong(MainDestinations.APP_ID_KEY),
102 | upPress = { actions.upPress(backStackEntry) }
103 | )
104 | }
105 |
106 | /*
107 | * Construct a nested NavGraph with app categories
108 | */
109 | navigation(
110 | route = MainDestinations.MAIN_ROUTE,
111 | startDestination = BottomNavTabs.Games.route
112 | ) {
113 | categories(
114 | onAppSelected = actions.openAppDetails,
115 | modifier = modifier
116 | )
117 | }
118 | }
119 | }
120 |
121 | /**
122 | * Models the navigation actions in the app.
123 | */
124 | class MainActions(
125 | navController: NavHostController,
126 | shouldShowAppBar: (Boolean) -> Unit
127 | ) {
128 | val openAppDetails = { appId: Long, from: NavBackStackEntry ->
129 | // In order to discard duplicated navigation events, we check the Lifecycle
130 | if (from.lifecycleIsResumed()) {
131 | shouldShowAppBar(false)
132 | navController.navigate(route = "${MainDestinations.APP_DETAILS_ROUTE}/$appId") {
133 | //popUpTo = navController.graph.startDestination
134 | launchSingleTop = true
135 | }
136 | }
137 | }
138 | val switchAppCategory = { tabRoute: String, currentRoute: String ->
139 | //shouldShowAppBar(true)
140 | if (tabRoute != currentRoute) {
141 | navController.navigate(route = tabRoute) {
142 | // Pop up to the start destination of the graph to avoid building up a large
143 | // stack of destinations on the back stack as users select items
144 | popUpTo(navController.graph.startDestinationId)
145 | // Avoid multiple copies of the same destination when re-selecting the same item
146 | launchSingleTop = true
147 | }
148 | }
149 | }
150 | val upPress: (rom: NavBackStackEntry) -> Unit = { from ->
151 | // In order to discard duplicated navigation events, we check the Lifecycle
152 | if (from.lifecycleIsResumed()) {
153 | shouldShowAppBar(true)
154 | navController.navigateUp()
155 | }
156 | }
157 | }
158 |
159 | /**
160 | * If the lifecycle is not resumed it means this NavBackStackEntry already processed a nav event.
161 | *
162 | * This is used to de-duplicate navigation events.
163 | */
164 | private fun NavBackStackEntry.lifecycleIsResumed() =
165 | this.lifecycle.currentState == Lifecycle.State.RESUMED
166 |
167 | fun NavGraphBuilder.categories(
168 | onAppSelected: (Long, NavBackStackEntry) -> Unit,
169 | modifier: Modifier = Modifier
170 | ) {
171 | composable(BottomNavTabs.Games.route) { from ->
172 | Games(
173 | onAppSelected = { id -> onAppSelected(id, from) },
174 | modifier = modifier
175 | )
176 | }
177 | composable(BottomNavTabs.Apps.route) { from ->
178 | Apps(
179 | onAppSelected = { id -> onAppSelected(id, from) },
180 | modifier = modifier
181 | )
182 | }
183 | composable(BottomNavTabs.Movies.route) {
184 | Movies(modifier = modifier)
185 | }
186 | composable(BottomNavTabs.Books.route) {
187 | Books()
188 | }
189 | }
190 |
191 | enum class AppsCategory {
192 | ForYou,
193 | TopCharts,
194 | Categories,
195 | EditorsChoice,
196 | EarlyAccess
197 | }
198 |
199 | enum class MoviesCategory {
200 | ForYou,
201 | TopSelling,
202 | NewReleases
203 | }
204 |
205 | enum class BottomNavTabs(
206 | @StringRes val title: Int,
207 | val icon: ImageVector,
208 | val route: String
209 | ) {
210 | Games(R.string.home_games, Icons.Outlined.Games, BottomNavDestinations.GAMES_ROUTE),
211 | Apps(R.string.home_apps, Icons.Outlined.Apps, BottomNavDestinations.APPS_ROUTE),
212 | Movies(R.string.home_movies, Icons.Outlined.Movie, BottomNavDestinations.MOVIES_ROUTE),
213 | Books(R.string.home_books, Icons.Outlined.Book, BottomNavDestinations.BOOKS_ROUTE)
214 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/PlayApp.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui
2 |
3 | import androidx.compose.foundation.layout.padding
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.tooling.preview.Preview
9 | import androidx.navigation.compose.rememberNavController
10 | import com.example.play.theme.PlayTheme
11 | import com.example.play.ui.components.PlayScaffold
12 | import com.example.play.ui.components.PlayToolBar
13 | import com.google.accompanist.insets.ProvideWindowInsets
14 |
15 | @Composable
16 | fun PlayApp(finishActivity: () -> Unit) {
17 |
18 | /*
19 | * NavController is stateful and keeps track of the back stack of composables
20 | * that make up the screens in your app and the state of each screen.
21 | */
22 | val navController = rememberNavController()
23 | val (shouldShowAppBar, updateAppBarVisibility) = remember { mutableStateOf(true) }
24 | val navActions = remember(navController) { MainActions(navController, updateAppBarVisibility) }
25 |
26 | ProvideWindowInsets {
27 | PlayTheme {
28 | val tabItems = remember { BottomNavTabs.values() }
29 | PlayScaffold(
30 | topBar = { PlayToolBar(shouldShow = shouldShowAppBar) },
31 | bottomBar = {
32 | PlayBottomNav(
33 | navController = navController,
34 | tabs = tabItems,
35 | onTabSelected = navActions.switchAppCategory
36 | )
37 | }
38 | ) { innerPaddingModifier ->
39 | NavGraph(
40 | finishActivity = finishActivity,
41 | navController = navController,
42 | modifier = Modifier.padding(innerPaddingModifier),
43 | actions = navActions,
44 | shouldShowAppBar = updateAppBarVisibility
45 | )
46 | }
47 | }
48 | }
49 | }
50 |
51 | @Preview
52 | @Composable
53 | private fun PlayAppPreview() {
54 | PlayTheme {
55 | PlayApp {}
56 | }
57 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/Tabs.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.foundation.layout.width
7 | import androidx.compose.foundation.shape.RoundedCornerShape
8 | import androidx.compose.material.ScrollableTabRow
9 | import androidx.compose.material.Tab
10 | import androidx.compose.material.TabPosition
11 | import androidx.compose.material.TabRowDefaults.tabIndicatorOffset
12 | import androidx.compose.material.Text
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.graphics.Color
16 | import androidx.compose.ui.res.stringResource
17 | import androidx.compose.ui.text.TextStyle
18 | import androidx.compose.ui.text.font.FontWeight
19 | import androidx.compose.ui.tooling.preview.Preview
20 | import androidx.compose.ui.unit.dp
21 | import androidx.compose.ui.unit.sp
22 | import com.example.play.R
23 | import com.example.play.theme.PlayTheme
24 | import com.example.play.ui.AppsCategory.Categories
25 | import com.example.play.ui.AppsCategory.EarlyAccess
26 | import com.example.play.ui.AppsCategory.EditorsChoice
27 | import com.example.play.ui.AppsCategory.ForYou
28 | import com.example.play.ui.AppsCategory.TopCharts
29 | import com.example.play.ui.MoviesCategory.NewReleases
30 | import com.example.play.ui.MoviesCategory.TopSelling
31 |
32 | @Composable
33 | fun AppsCategoryTabs(
34 | categories: List,
35 | selectedCategory: AppsCategory,
36 | onCategorySelected: (AppsCategory) -> Unit
37 | ) {
38 | val selectedIndex = categories.indexOfFirst { it == selectedCategory }
39 | val indicator = @Composable { tabPositions: List ->
40 | HomeCategoryTabIndicator(
41 | Modifier.tabIndicatorOffset(tabPositions[selectedIndex])
42 | )
43 | }
44 |
45 | ScrollableTabRow(
46 | selectedTabIndex = selectedIndex,
47 | indicator = indicator,
48 | backgroundColor = PlayTheme.colors.uiBackground,
49 | edgePadding = 32.dp
50 | ) {
51 | categories.forEachIndexed { index, category ->
52 | Tab(
53 | selected = index == selectedIndex,
54 | onClick = { onCategorySelected(category) },
55 | modifier = Modifier.background(color = PlayTheme.colors.uiBackground),
56 | text = {
57 | Text(
58 | text = when (category) {
59 | ForYou -> stringResource(R.string.for_you)
60 | TopCharts -> stringResource(R.string.top_charts)
61 | Categories -> stringResource(R.string.categories)
62 | EditorsChoice -> stringResource(R.string.editors_choice)
63 | EarlyAccess -> stringResource(R.string.early_access)
64 | },
65 | color = if (index == selectedIndex) {
66 | PlayTheme.colors.accent
67 | } else {
68 | PlayTheme.colors.textPrimary
69 | },
70 | style = TextStyle(
71 | fontWeight = FontWeight.Normal,
72 | fontSize = 12.sp
73 | )
74 | )
75 | }
76 | )
77 | }
78 | }
79 | }
80 |
81 | @Composable
82 | fun MoviesCategoryTabs(
83 | categories: List,
84 | selectedCategory: MoviesCategory,
85 | onCategorySelected: (MoviesCategory) -> Unit
86 | ) {
87 | val selectedIndex = categories.indexOfFirst { it == selectedCategory }
88 | val indicator = @Composable { tabPositions: List ->
89 | HomeCategoryTabIndicator(
90 | Modifier.tabIndicatorOffset(tabPositions[selectedIndex])
91 | )
92 | }
93 |
94 | ScrollableTabRow(
95 | selectedTabIndex = selectedIndex,
96 | indicator = indicator,
97 | backgroundColor = PlayTheme.colors.uiBackground,
98 | edgePadding = 32.dp
99 | ) {
100 | categories.forEachIndexed { index, category ->
101 | Tab(
102 | selected = index == selectedIndex,
103 | onClick = { onCategorySelected(category) },
104 | modifier = Modifier.background(color = PlayTheme.colors.uiBackground),
105 | text = {
106 | Text(
107 | text = when (category) {
108 | MoviesCategory.ForYou -> stringResource(R.string.for_you)
109 | TopSelling -> stringResource(R.string.top_selling)
110 | NewReleases -> stringResource(R.string.new_releases)
111 | },
112 | color = if (index == selectedIndex) {
113 | PlayTheme.colors.accent
114 | } else {
115 | PlayTheme.colors.textPrimary
116 | },
117 | style = TextStyle(
118 | fontWeight = FontWeight.Normal,
119 | fontSize = 12.sp
120 | )
121 | )
122 | }
123 | )
124 | }
125 | }
126 | }
127 |
128 | @Composable
129 | fun HomeCategoryTabIndicator(
130 | modifier: Modifier = Modifier,
131 | color: Color = PlayTheme.colors.accent
132 | ) {
133 | Spacer(
134 | modifier
135 | .width(5.dp)
136 | .height(3.dp)
137 | .background(color, RoundedCornerShape(percent = 50))
138 | )
139 | }
140 |
141 | @Preview
142 | @Composable
143 | fun AppCategoryTabsPreview() {
144 | PlayTheme {
145 | AppsCategoryTabs(
146 | categories = listOf(
147 | ForYou, TopCharts, Categories, EditorsChoice, EarlyAccess
148 | ),
149 | selectedCategory = ForYou,
150 | onCategorySelected = {}
151 | )
152 | }
153 | }
154 |
155 | @Preview
156 | @Composable
157 | fun AppCategoryTabsDarkPreview() {
158 | PlayTheme(darkTheme = true) {
159 | AppsCategoryTabs(
160 | categories = listOf(
161 | ForYou, TopCharts, Categories, EditorsChoice, EarlyAccess
162 | ),
163 | selectedCategory = ForYou,
164 | onCategorySelected = {}
165 | )
166 | }
167 | }
168 |
169 | @Preview
170 | @Composable
171 | fun MovieCategoryTabsPreview() {
172 | PlayTheme {
173 | MoviesCategoryTabs(
174 | categories = listOf(
175 | MoviesCategory.ForYou, TopSelling, NewReleases
176 | ),
177 | selectedCategory = MoviesCategory.ForYou,
178 | onCategorySelected = {}
179 | )
180 | }
181 | }
182 |
183 | @Preview
184 | @Composable
185 | fun MovieCategoryTabsDarkPreview() {
186 | PlayTheme(darkTheme = true) {
187 | MoviesCategoryTabs(
188 | categories = listOf(
189 | MoviesCategory.ForYou, TopSelling, NewReleases
190 | ),
191 | selectedCategory = MoviesCategory.ForYou,
192 | onCategorySelected = {}
193 | )
194 | }
195 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/apps/Apps.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.apps
2 |
3 | import androidx.compose.animation.Crossfade
4 | import androidx.compose.animation.core.LinearOutSlowInEasing
5 | import androidx.compose.animation.core.TweenSpec
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.mutableStateOf
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.runtime.saveable.rememberSaveable
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.tooling.preview.Preview
14 | import com.example.play.data.AppRepo
15 | import com.example.play.theme.PlayTheme
16 | import com.example.play.ui.AppsCategory
17 | import com.example.play.ui.AppsCategory.Categories
18 | import com.example.play.ui.AppsCategory.EarlyAccess
19 | import com.example.play.ui.AppsCategory.EditorsChoice
20 | import com.example.play.ui.AppsCategory.ForYou
21 | import com.example.play.ui.AppsCategory.TopCharts
22 | import com.example.play.ui.apps.applist.ForYouLayout
23 | import com.example.play.ui.apps.applist.TopChartsLayout
24 | import com.example.play.ui.components.PlaySurface
25 | import com.example.play.ui.AppsCategoryTabs
26 | import com.google.accompanist.insets.navigationBarsPadding
27 |
28 | @Composable
29 | fun Apps(
30 | onAppSelected: (Long) -> Unit,
31 | modifier: Modifier = Modifier,
32 | appsCategories: List = getAppCategoriesList()
33 | ) {
34 | val forYouData = remember { AppRepo.getForYouApps() }
35 | val topChartsData = remember { AppRepo.getTopChartsApps() }
36 | val (currentCategory, setCurrentCategory) = rememberSaveable { mutableStateOf(ForYou) }
37 |
38 | PlaySurface(modifier = modifier.fillMaxSize()) {
39 | Column(modifier = Modifier.navigationBarsPadding(left = true, right = true)) {
40 | AppsCategoryTabs(
41 | categories = appsCategories,
42 | selectedCategory = currentCategory,
43 | onCategorySelected = setCurrentCategory
44 | )
45 | val tweenSpec = remember { getAnimSpec() }
46 | Crossfade(currentCategory, animationSpec = tweenSpec) { category ->
47 | when (category) {
48 | ForYou -> ForYouLayout(forYouData, onAppSelected = onAppSelected)
49 | TopCharts -> TopChartsLayout(topChartsData, onAppSelected = onAppSelected)
50 | else -> ForYouLayout(forYouData, onAppSelected = onAppSelected)
51 | }
52 | }
53 | }
54 | }
55 | }
56 |
57 | private fun getAnimSpec(): TweenSpec {
58 | return TweenSpec(
59 | durationMillis = 600,
60 | easing = LinearOutSlowInEasing
61 | )
62 | }
63 |
64 | private fun getAppCategoriesList() = listOf(
65 | ForYou, TopCharts, Categories, EditorsChoice, EarlyAccess
66 | )
67 |
68 | @Preview("Apps")
69 | @Composable
70 | fun AppsPreview() {
71 | PlayTheme {
72 | Apps(
73 | onAppSelected = {}
74 | )
75 | }
76 | }
77 |
78 | @Preview("Apps • Dark Theme")
79 | @Composable
80 | fun AppsDarkPreview() {
81 | PlayTheme(darkTheme = true) {
82 | Apps(
83 | onAppSelected = {}
84 | )
85 | }
86 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/apps/applist/ForYou.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.apps.applist
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.layout.width
9 | import androidx.compose.foundation.lazy.LazyColumn
10 | import androidx.compose.foundation.lazy.LazyRow
11 | import androidx.compose.foundation.lazy.items
12 | import androidx.compose.material.Icon
13 | import androidx.compose.material.IconButton
14 | import androidx.compose.material.Text
15 | import androidx.compose.material.icons.Icons
16 | import androidx.compose.material.icons.outlined.ArrowForward
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.runtime.key
19 | import androidx.compose.ui.Alignment
20 | import androidx.compose.ui.Modifier
21 | import androidx.compose.ui.text.TextStyle
22 | import androidx.compose.ui.text.font.FontWeight
23 | import androidx.compose.ui.text.style.TextOverflow
24 | import androidx.compose.ui.tooling.preview.Preview
25 | import androidx.compose.ui.unit.dp
26 | import androidx.compose.ui.unit.sp
27 | import com.example.play.data.App
28 | import com.example.play.data.AppCollection
29 | import com.example.play.data.AppRepo
30 | import com.example.play.data.CollectionType
31 | import com.example.play.theme.PlayTheme
32 | import com.google.accompanist.insets.navigationBarsPadding
33 |
34 | @Composable
35 | fun ForYouLayout(
36 | data: List,
37 | onAppSelected: (Long) -> Unit,
38 | modifier: Modifier = Modifier
39 | ) {
40 | Spacer(
41 | modifier = Modifier
42 | .height(4.dp)
43 | )
44 | LazyColumn(modifier = modifier) {
45 | items(data) { appCollection ->
46 | key(appCollection.id) {
47 | ForYou(
48 | appCollection = appCollection,
49 | onAppSelected = onAppSelected
50 | )
51 | }
52 | }
53 | }
54 | Spacer(
55 | modifier = Modifier
56 | .navigationBarsPadding(left = false, right = false)
57 | .height(8.dp)
58 | )
59 | }
60 |
61 | @Composable
62 | fun ForYou(
63 | appCollection: AppCollection,
64 | onAppSelected: (Long) -> Unit,
65 | modifier: Modifier = Modifier,
66 | featured: Boolean = true
67 | ) {
68 | Column(modifier = modifier) {
69 | Row(
70 | verticalAlignment = Alignment.CenterVertically,
71 | modifier = Modifier
72 | .padding(start = 24.dp)
73 | ) {
74 | Text(
75 | text = appCollection.name,
76 | style = TextStyle(
77 | fontWeight = FontWeight.Medium,
78 | fontSize = 16.sp,
79 | letterSpacing = 0.15.sp
80 | ),
81 | color = PlayTheme.colors.textPrimary,
82 | maxLines = 1,
83 | overflow = TextOverflow.Ellipsis,
84 | modifier = Modifier.weight(1f)
85 | )
86 | IconButton(
87 | onClick = {},
88 | modifier = Modifier.align(Alignment.CenterVertically)
89 | ) {
90 | Icon(
91 | imageVector = Icons.Outlined.ArrowForward,
92 | tint = PlayTheme.colors.iconTint,
93 | contentDescription = null
94 | )
95 | }
96 | }
97 | if (featured && appCollection.type == CollectionType.Featured) {
98 | FeaturedAppsList(appCollection.apps, onAppSelected)
99 | } else {
100 | AppsList(appCollection.apps, onAppSelected)
101 | }
102 | }
103 | }
104 |
105 | @Composable
106 | private fun FeaturedAppsList(
107 | apps: List,
108 | onAppSelected: (Long) -> Unit
109 | ) {
110 | LazyRow(modifier = Modifier.padding(start = 16.dp)) {
111 | items(apps) { app ->
112 | PlayFeaturedAppItem(app, onAppSelected = onAppSelected)
113 | Spacer(modifier = Modifier.width(1.dp))
114 | }
115 | }
116 | }
117 |
118 | @Composable
119 | private fun AppsList(
120 | apps: List,
121 | onAppSelected: (Long) -> Unit
122 | ) {
123 | LazyRow(modifier = Modifier.padding(start = 16.dp)) {
124 | items(apps) { app ->
125 | AppItem(app = app, onAppSelected = onAppSelected)
126 | Spacer(modifier = Modifier.width(1.dp))
127 | }
128 | }
129 | }
130 |
131 | @Preview("For You list preview")
132 | @Composable
133 | fun ForYouListPreview() {
134 | PlayTheme {
135 | ForYouLayout(data = AppRepo.getApps(), onAppSelected = {})
136 | }
137 | }
138 |
139 | @Preview("For You list dark preview")
140 | @Composable
141 | fun ForYouListDarkPreview() {
142 | PlayTheme(darkTheme = true) {
143 | ForYouLayout(data = AppRepo.getApps(), onAppSelected = {})
144 | }
145 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/apps/applist/TopCharts.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.apps.applist
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.foundation.lazy.LazyColumn
7 | import androidx.compose.foundation.lazy.items
8 | import androidx.compose.material.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.mutableStateOf
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.text.TextStyle
15 | import androidx.compose.ui.text.font.FontWeight
16 | import androidx.compose.ui.tooling.preview.Preview
17 | import androidx.compose.ui.unit.dp
18 | import androidx.compose.ui.unit.sp
19 | import com.example.play.data.App
20 | import com.example.play.data.AppRepo
21 | import com.example.play.theme.PlayTheme
22 | import com.example.play.ui.components.FilterBar
23 | import com.example.play.ui.components.Switch
24 |
25 | @Composable
26 | fun TopChartsLayout(
27 | appCollection: List,
28 | onAppSelected: (Long) -> Unit,
29 | modifier: Modifier = Modifier
30 | ) {
31 | val (filterSelected, setFilterSelected: (Int) -> Unit) = remember { mutableStateOf(1) }
32 | Column(modifier = modifier) {
33 | TopChartsHeader(filterSelected, setFilterSelected)
34 | TopChartAppsList(
35 | appCollection.filter { app ->
36 | app.filterCategory == AppRepo.getFilter(filterSelected)?.name
37 | },
38 | onAppSelected = onAppSelected
39 | )
40 | }
41 | }
42 |
43 | @Composable
44 | private fun TopChartsHeader(
45 | filterSelected: Int,
46 | setFilterSelected: (Int) -> Unit
47 | ) {
48 | val (switchState, updateSwitchState) = remember { mutableStateOf(true) }
49 | Column {
50 | Row(
51 | verticalAlignment = Alignment.CenterVertically,
52 | modifier = Modifier
53 | .padding(start = 24.dp, end = 24.dp, top = 16.dp, bottom = 8.dp)
54 | ) {
55 | Text(
56 | text = "Show installed apps",
57 | style = TextStyle(
58 | fontWeight = FontWeight.Normal,
59 | fontSize = 14.sp
60 | ),
61 | color = PlayTheme.colors.textSecondary,
62 | modifier = Modifier.weight(1f)
63 | )
64 | Switch(
65 | switchState = switchState,
66 | updateSwitchState = updateSwitchState,
67 | modifier = Modifier.align(Alignment.CenterVertically)
68 | )
69 | }
70 | FilterBar(filters = AppRepo.getFilters(), filterSelected, setFilterSelected)
71 | }
72 | }
73 |
74 | @Composable
75 | private fun TopChartAppsList(
76 | apps: List,
77 | onAppSelected: (Long) -> Unit,
78 | modifier: Modifier = Modifier
79 | ) {
80 | LazyColumn(modifier = modifier) {
81 | items(apps) { app ->
82 | TopChartAppItem(app, onAppSelected = onAppSelected)
83 | }
84 | }
85 | }
86 |
87 | @Preview("Top Charts preview")
88 | @Composable
89 | fun TopChartsPreview() {
90 | PlayTheme {
91 | TopChartsLayout(
92 | appCollection = AppRepo.getTopChartsApps(),
93 | onAppSelected = {}
94 | )
95 | }
96 | }
97 |
98 | @Preview("Top Charts dark preview")
99 | @Composable
100 | fun TopChartsDarkPreview() {
101 | PlayTheme(darkTheme = true) {
102 | TopChartsLayout(
103 | appCollection = AppRepo.getTopChartsApps(),
104 | onAppSelected = {}
105 | )
106 | }
107 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/books/Books.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.books
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.text.TextStyle
11 | import androidx.compose.ui.text.font.FontWeight
12 | import androidx.compose.ui.text.style.TextAlign.Center
13 | import androidx.compose.ui.tooling.preview.Preview
14 | import androidx.compose.ui.unit.dp
15 | import androidx.compose.ui.unit.sp
16 | import com.example.play.theme.PlayTheme
17 |
18 | @Composable
19 | fun Books(
20 | ) {
21 | Column(
22 | modifier = Modifier.padding(horizontal = 16.dp),
23 | horizontalAlignment = Alignment.CenterHorizontally
24 | ) {
25 | //LottieLoadingView()
26 | Text(
27 | text = "Coming Soon",
28 | style = TextStyle(
29 | fontWeight = FontWeight.Medium,
30 | fontSize = 16.sp,
31 | letterSpacing = 0.15.sp
32 | ),
33 | color = PlayTheme.colors.textSecondary,
34 | textAlign = Center,
35 | modifier = Modifier.padding(16.dp)
36 | )
37 | }
38 | }
39 |
40 | /**
41 | * Commented out because of build issue in snapshot version of lottie compose
42 | */
43 | //@Composable
44 | //fun LottieLoadingView(modifier: Modifier = Modifier) {
45 | // val animationSpec = remember { LottieAnimationSpec.RawRes(R.raw.working) }
46 | // LottieAnimation(
47 | // animationSpec,
48 | // modifier = Modifier
49 | // .size(250.dp)
50 | // .then(modifier)
51 | // )
52 | //}
53 |
54 | @Preview
55 | @Composable
56 | private fun PlayBooksPreview() {
57 | PlayTheme {
58 | Books()
59 | }
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/components/AppBarLayout.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.components
2 |
3 | import androidx.compose.foundation.layout.Row
4 | import androidx.compose.foundation.layout.fillMaxWidth
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.foundation.layout.size
7 | import androidx.compose.material.Icon
8 | import androidx.compose.material.IconButton
9 | import androidx.compose.material.icons.Icons.Outlined
10 | import androidx.compose.material.icons.outlined.ArrowBack
11 | import androidx.compose.material.icons.outlined.MoreVert
12 | import androidx.compose.material.icons.outlined.Search
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.tooling.preview.Preview
16 | import androidx.compose.ui.unit.dp
17 | import com.example.play.theme.PlayTheme
18 | import com.google.accompanist.insets.statusBarsPadding
19 |
20 | @Composable
21 | fun AppBarLayout(
22 | upPress: () -> Unit
23 | ) {
24 | Row(
25 | modifier = Modifier
26 | .fillMaxWidth()
27 | .statusBarsPadding()
28 | .padding(horizontal = 16.dp, vertical = 16.dp)
29 | .size(24.dp)
30 | ) {
31 | BackButton(
32 | modifier = Modifier.weight(1f),
33 | upPress = upPress
34 | )
35 | SearchButton(Modifier)
36 | MoreButton(Modifier)
37 | }
38 | }
39 |
40 | @Composable
41 | fun BackButton(
42 | modifier: Modifier,
43 | upPress: () -> Unit
44 | ) {
45 | PlaySurface(modifier = modifier) {
46 | IconButton(
47 | onClick = { upPress() }
48 | ) {
49 | Icon(
50 | imageVector = Outlined.ArrowBack,
51 | tint = PlayTheme.colors.iconTint,
52 | contentDescription = null
53 | )
54 | }
55 | }
56 | }
57 |
58 | @Composable
59 | fun MoreButton(modifier: Modifier) {
60 | IconButton(
61 | onClick = {},
62 | modifier = modifier
63 | ) {
64 | Icon(
65 | imageVector = Outlined.MoreVert,
66 | tint = PlayTheme.colors.iconTint,
67 | contentDescription = null
68 | )
69 | }
70 | }
71 |
72 | @Composable
73 | fun SearchButton(modifier: Modifier) {
74 | IconButton(
75 | onClick = {},
76 | modifier = modifier
77 | ) {
78 | Icon(
79 | imageVector = Outlined.Search,
80 | tint = PlayTheme.colors.iconTint,
81 | contentDescription = null
82 | )
83 | }
84 | }
85 |
86 | @Preview("Back Button")
87 | @Composable
88 | private fun BackButtonPreview() {
89 | PlayTheme {
90 | PlaySurface {
91 | AppBarLayout(
92 | upPress = {}
93 | )
94 | }
95 | }
96 | }
97 |
98 | @Preview("Back Button Dark")
99 | @Composable
100 | private fun BackButtonDarkPreview() {
101 | PlayTheme(darkTheme = true) {
102 | PlaySurface {
103 | AppBarLayout(
104 | upPress = {}
105 | )
106 | }
107 | }
108 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/components/Card.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.components
2 |
3 | import androidx.compose.foundation.BorderStroke
4 | import androidx.compose.material.MaterialTheme
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.graphics.Color
8 | import androidx.compose.ui.graphics.Shape
9 | import androidx.compose.ui.unit.Dp
10 | import androidx.compose.ui.unit.dp
11 | import com.example.play.theme.PlayTheme
12 |
13 | @Composable
14 | fun PlayCard(
15 | modifier: Modifier = Modifier,
16 | shape: Shape = MaterialTheme.shapes.medium,
17 | color: Color = PlayTheme.colors.uiBackground,
18 | contentColor: Color = PlayTheme.colors.textPrimary,
19 | border: BorderStroke? = null,
20 | elevation: Dp = 1.dp,
21 | content: @Composable () -> Unit
22 | ) {
23 | PlaySurface(
24 | modifier = modifier,
25 | shape = shape,
26 | color = color,
27 | contentColor = contentColor,
28 | elevation = elevation,
29 | border = border,
30 | content = content
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/components/Divider.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.components
2 |
3 | import androidx.compose.material.Divider
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.graphics.Color
7 | import androidx.compose.ui.unit.Dp
8 | import androidx.compose.ui.unit.dp
9 | import com.example.play.theme.PlayTheme
10 |
11 | @Composable
12 | fun PlayDivider(
13 | modifier: Modifier = Modifier,
14 | color: Color = PlayTheme.colors.uiBorder.copy(alpha = DividerAlpha),
15 | thickness: Dp = 1.dp,
16 | startIndent: Dp = 0.dp
17 | ) {
18 | Divider(
19 | modifier = modifier,
20 | color = color,
21 | thickness = thickness,
22 | startIndent = startIndent
23 | )
24 | }
25 |
26 | private const val DividerAlpha = 0.12f
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/components/Filters.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.components
2 |
3 | import androidx.compose.foundation.border
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.foundation.layout.heightIn
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.layout.width
10 | import androidx.compose.foundation.lazy.LazyRow
11 | import androidx.compose.foundation.selection.toggleable
12 | import androidx.compose.foundation.shape.RoundedCornerShape
13 | import androidx.compose.material.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.getValue
16 | import androidx.compose.runtime.remember
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.graphics.Shape
19 | import androidx.compose.ui.text.TextStyle
20 | import androidx.compose.ui.text.font.FontWeight
21 | import androidx.compose.ui.tooling.preview.Preview
22 | import androidx.compose.ui.unit.dp
23 | import androidx.compose.ui.unit.sp
24 | import com.example.play.anim.getFilterBgColorState
25 | import com.example.play.anim.getFilterTextColorState
26 | import com.example.play.data.AppRepo
27 | import com.example.play.data.Filter
28 | import com.example.play.theme.PlayTheme
29 |
30 | @Composable
31 | fun FilterBar(
32 | filters: List,
33 | filterSelected: Int = AppRepo.getFilters().first().id.value,
34 | setFilterSelected: (Int) -> Unit = {}
35 | ) {
36 | LazyRow(modifier = Modifier.heightIn(min = 56.dp)) {
37 | item {
38 | Spacer(modifier = Modifier.width(24.dp))
39 | filters.forEach { filter ->
40 | filter.enabled.value = filterSelected == filter.id.value
41 | val (selected, setSelected) = remember { filter.enabled }
42 | FilterChip(
43 | filter = filter,
44 | selected = selected,
45 | setSelected = setSelected,
46 | setFilterSelected = setFilterSelected
47 | )
48 | Spacer(modifier = Modifier.width(8.dp))
49 | }
50 | }
51 | }
52 | }
53 |
54 | @Composable
55 | fun FilterChip(
56 | filter: Filter,
57 | modifier: Modifier = Modifier,
58 | shape: Shape = RoundedCornerShape(50),
59 | selected: Boolean = false,
60 | setSelected: (Boolean) -> Unit,
61 | setFilterSelected: (Int) -> Unit
62 | ) {
63 |
64 | val backgroundColor by getFilterBgColorState(isSelected = selected)
65 | val textColor by getFilterTextColorState(isSelected = selected)
66 |
67 | PlaySurface(
68 | modifier = modifier
69 | .height(30.dp)
70 | .border(
71 | 1.dp, if (selected) PlayTheme.colors.accent.copy(
72 | alpha = 0.1f
73 | ) else PlayTheme.colors.uiBorder, shape
74 | ),
75 | color = backgroundColor,
76 | contentColor = textColor,
77 | shape = shape
78 | ) {
79 | Box(
80 | modifier = Modifier.toggleable(
81 | value = selected,
82 | onValueChange = {
83 | setSelected(true)
84 | setFilterSelected(filter.id.value)
85 | }
86 | )
87 | ) {
88 | Text(
89 | text = filter.name,
90 | style = TextStyle(
91 | fontWeight = FontWeight.Normal,
92 | fontSize = 13.sp,
93 | letterSpacing = 0.15.sp
94 | ),
95 | maxLines = 1,
96 | modifier = Modifier.padding(
97 | horizontal = 20.dp,
98 | vertical = 6.dp
99 | )
100 | )
101 | }
102 | }
103 | }
104 |
105 | @Preview
106 | @Composable
107 | fun FilterBarPreview() {
108 | PlayTheme {
109 | FilterBar(filters = AppRepo.getFilters())
110 | }
111 | }
112 |
113 | @Preview
114 | @Composable
115 | fun FilterBarDarkPreview() {
116 | PlayTheme(darkTheme = true) {
117 | FilterBar(filters = AppRepo.getFilters())
118 | }
119 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/components/Gradient.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.play.ui.components
18 |
19 | import androidx.compose.animation.animateColorAsState
20 | import androidx.compose.foundation.background
21 | import androidx.compose.foundation.border
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.composed
24 | import androidx.compose.ui.draw.drawWithContent
25 | import androidx.compose.ui.graphics.BlendMode
26 | import androidx.compose.ui.graphics.Brush
27 | import androidx.compose.ui.graphics.Color
28 | import androidx.compose.ui.graphics.Shape
29 | import androidx.compose.ui.graphics.TileMode
30 | import androidx.compose.ui.unit.Dp
31 | import androidx.compose.ui.unit.dp
32 |
33 | fun Modifier.diagonalGradientTint(
34 | colors: List,
35 | blendMode: BlendMode
36 | ) = drawWithContent {
37 | drawContent()
38 | drawRect(
39 | brush = Brush.linearGradient(colors),
40 | blendMode = blendMode
41 | )
42 | }
43 |
44 | fun Modifier.offsetGradientBackground(
45 | colors: List,
46 | width: Float,
47 | offset: Float = 0f
48 | ) = background(
49 | Brush.horizontalGradient(
50 | colors,
51 | startX = -offset,
52 | endX = width - offset,
53 | tileMode = TileMode.Mirror
54 | )
55 | )
56 |
57 | fun Modifier.diagonalGradientBorder(
58 | colors: List,
59 | borderSize: Dp = 2.dp,
60 | shape: Shape
61 | ) = border(
62 | width = borderSize,
63 | brush = Brush.linearGradient(colors),
64 | shape = shape
65 | )
66 |
67 | fun Modifier.fadeInDiagonalGradientBorder(
68 | showBorder: Boolean,
69 | colors: List,
70 | borderSize: Dp = 2.dp,
71 | shape: Shape
72 | ) = composed {
73 | val animatedColors = List(colors.size) { i ->
74 | animateColorAsState(if (showBorder) colors[i] else colors[i].copy(alpha = 0f)).value
75 | }
76 | diagonalGradientBorder(
77 | colors = animatedColors,
78 | borderSize = borderSize,
79 | shape = shape
80 | )
81 | }
82 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/components/Image.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.components
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.layout.size
6 | import androidx.compose.foundation.shape.CircleShape
7 | import androidx.compose.foundation.shape.RoundedCornerShape
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.layout.ContentScale
12 | import androidx.compose.ui.res.painterResource
13 | import androidx.compose.ui.tooling.preview.Preview
14 | import androidx.compose.ui.unit.Dp
15 | import androidx.compose.ui.unit.dp
16 | import com.example.play.theme.PlayTheme
17 | import com.google.accompanist.coil.rememberCoilPainter
18 |
19 | @Composable
20 | fun CircularAppImage(
21 | imageUrl: String,
22 | modifier: Modifier = Modifier,
23 | elevation: Dp = 0.dp
24 | ) {
25 | PlaySurface(
26 | color = Color.LightGray,
27 | elevation = elevation,
28 | shape = CircleShape,
29 | modifier = modifier
30 | ) {
31 | Image(
32 | painter = rememberCoilPainter(
33 | request = imageUrl
34 | ),
35 | contentScale = ContentScale.Crop,
36 | modifier = Modifier.fillMaxSize(),
37 | contentDescription = null
38 | )
39 | }
40 | }
41 |
42 | @Composable
43 | fun CircularLocalImage(
44 | resId: Int,
45 | modifier: Modifier = Modifier,
46 | elevation: Dp = 0.dp
47 | ) {
48 | PlaySurface(
49 | color = Color.LightGray,
50 | elevation = elevation,
51 | shape = CircleShape,
52 | modifier = modifier
53 | ) {
54 | Image(
55 | painterResource(resId),
56 | contentDescription = null,
57 | contentScale = ContentScale.Crop,
58 | modifier = Modifier.fillMaxSize()
59 | )
60 | }
61 | }
62 |
63 | @Composable
64 | fun RoundedCornerAppImage(
65 | imageUrl: String,
66 | modifier: Modifier = Modifier,
67 | cornerPercent: Int,
68 | elevation: Dp = 0.dp
69 | ) {
70 | PlaySurface(
71 | color = Color.LightGray,
72 | elevation = elevation,
73 | shape = RoundedCornerShape(cornerPercent),
74 | modifier = modifier
75 | ) {
76 | Image(
77 | painter = rememberCoilPainter(
78 | request = imageUrl
79 | ),
80 | contentScale = ContentScale.Crop,
81 | modifier = Modifier.fillMaxSize(),
82 | contentDescription = null
83 | )
84 | }
85 | }
86 |
87 | @Preview("Circular App Image")
88 | @Composable
89 | fun CircularAppImagePreview() {
90 | PlayTheme(darkTheme = true) {
91 | CircularAppImage(
92 | imageUrl = "",
93 | modifier = Modifier
94 | .size(120.dp),
95 | elevation = 10.dp
96 | )
97 | }
98 | }
99 |
100 | @Preview("Rounded Corner App Image")
101 | @Composable
102 | fun RoundedCornerAppImagePreview() {
103 | PlayTheme(darkTheme = true) {
104 | RoundedCornerAppImage(
105 | imageUrl = "",
106 | modifier = Modifier
107 | .size(120.dp),
108 | cornerPercent = 5,
109 | elevation = 10.dp
110 | )
111 | }
112 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/components/PlayToolBar.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.components
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.foundation.layout.sizeIn
6 | import androidx.compose.foundation.shape.RoundedCornerShape
7 | import androidx.compose.material.Icon
8 | import androidx.compose.material.IconButton
9 | import androidx.compose.material.Text
10 | import androidx.compose.material.TopAppBar
11 | import androidx.compose.material.icons.Icons
12 | import androidx.compose.material.icons.outlined.Menu
13 | import androidx.compose.material.icons.outlined.Mic
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.text.TextStyle
18 | import androidx.compose.ui.text.font.FontWeight
19 | import androidx.compose.ui.text.style.TextAlign
20 | import androidx.compose.ui.text.style.TextOverflow.Ellipsis
21 | import androidx.compose.ui.tooling.preview.Preview
22 | import androidx.compose.ui.unit.dp
23 | import androidx.compose.ui.unit.sp
24 | import com.example.play.R
25 | import com.example.play.theme.AlphaNearOpaque
26 | import com.example.play.theme.PlayTheme
27 | import com.google.accompanist.insets.statusBarsPadding
28 |
29 | @Composable
30 | fun PlayToolBar(
31 | modifier: Modifier = Modifier,
32 | shouldShow: Boolean
33 | ) {
34 | if (shouldShow) {
35 | PlaySurface(modifier = modifier.statusBarsPadding()) {
36 | PlaySurface(
37 | shape = RoundedCornerShape(5.dp),
38 | modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp, bottom = 8.dp),
39 | elevation = 4.dp,
40 | color = PlayTheme.colors.uiBackground
41 | ) {
42 | TopAppBar(
43 | backgroundColor = PlayTheme.colors.uiBackground.copy(alpha = AlphaNearOpaque),
44 | contentColor = PlayTheme.colors.textSecondary
45 | ) {
46 | IconButton(
47 | onClick = {},
48 | modifier = Modifier
49 | .align(Alignment.CenterVertically)
50 | .weight(1f, false)
51 | ) {
52 | Icon(
53 | imageVector = Icons.Outlined.Menu,
54 | tint = PlayTheme.colors.iconTint,
55 | contentDescription = null
56 | )
57 | }
58 | Text(
59 | text = "Search for apps & games",
60 | color = PlayTheme.colors.textSecondary,
61 | textAlign = TextAlign.Start,
62 | maxLines = 1,
63 | style = TextStyle(
64 | fontWeight = FontWeight.Light,
65 | fontSize = 16.sp
66 | ),
67 | overflow = Ellipsis,
68 | modifier = Modifier
69 | .align(Alignment.CenterVertically)
70 | .weight(4f, true)
71 | )
72 | IconButton(
73 | onClick = {},
74 | modifier = Modifier
75 | .align(Alignment.CenterVertically)
76 | .weight(1f, false)
77 | ) {
78 | Icon(
79 | imageVector = Icons.Outlined.Mic,
80 | tint = PlayTheme.colors.iconTint,
81 | contentDescription = null
82 | )
83 | }
84 | CircularLocalImage(
85 | resId = R.drawable.user_profile_pic,
86 | modifier = Modifier
87 | .sizeIn(maxHeight = 40.dp, maxWidth = 40.dp)
88 | .weight(1f, false)
89 | .clickable(onClick = {})
90 | )
91 | }
92 | }
93 | }
94 | }
95 | }
96 |
97 | @Preview
98 | @Composable
99 | fun ToolBarPreview() {
100 | PlayTheme {
101 | PlayToolBar(shouldShow = true)
102 | }
103 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/components/Scaffold.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.components
2 |
3 | import androidx.compose.foundation.layout.ColumnScope
4 | import androidx.compose.foundation.layout.PaddingValues
5 | import androidx.compose.material.DrawerDefaults
6 | import androidx.compose.material.ExperimentalMaterialApi
7 | import androidx.compose.material.FabPosition
8 | import androidx.compose.material.MaterialTheme
9 | import androidx.compose.material.Scaffold
10 | import androidx.compose.material.ScaffoldState
11 | import androidx.compose.material.SnackbarHost
12 | import androidx.compose.material.SnackbarHostState
13 | import androidx.compose.material.rememberScaffoldState
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.graphics.Shape
18 | import androidx.compose.ui.unit.Dp
19 | import com.example.play.theme.PlayTheme
20 |
21 | /**
22 | * Wrap Material [androidx.compose.material.Scaffold] and set [PlayTheme] colors.
23 | */
24 | @OptIn(ExperimentalMaterialApi::class)
25 | @Composable
26 | fun PlayScaffold(
27 | modifier: Modifier = Modifier,
28 | scaffoldState: ScaffoldState = rememberScaffoldState(),
29 | topBar: @Composable (() -> Unit) = {},
30 | bottomBar: @Composable (() -> Unit) = {},
31 | snackBarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
32 | floatingActionButton: @Composable (() -> Unit) = {},
33 | floatingActionButtonPosition: FabPosition = FabPosition.End,
34 | isFloatingActionButtonDocked: Boolean = false,
35 | drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
36 | drawerShape: Shape = MaterialTheme.shapes.large,
37 | drawerElevation: Dp = DrawerDefaults.Elevation,
38 | drawerBackgroundColor: Color = PlayTheme.colors.uiBackground,
39 | drawerContentColor: Color = PlayTheme.colors.textSecondary,
40 | drawerScrimColor: Color = PlayTheme.colors.uiBorder,
41 | backgroundColor: Color = PlayTheme.colors.uiBackground,
42 | contentColor: Color = PlayTheme.colors.textSecondary,
43 | bodyContent: @Composable (PaddingValues) -> Unit
44 | ) {
45 | Scaffold(
46 | modifier = modifier,
47 | scaffoldState = scaffoldState,
48 | topBar = topBar,
49 | bottomBar = bottomBar,
50 | snackbarHost = snackBarHost,
51 | floatingActionButton = floatingActionButton,
52 | floatingActionButtonPosition = floatingActionButtonPosition,
53 | isFloatingActionButtonDocked = isFloatingActionButtonDocked,
54 | drawerContent = drawerContent,
55 | drawerShape = drawerShape,
56 | drawerElevation = drawerElevation,
57 | drawerBackgroundColor = drawerBackgroundColor,
58 | drawerContentColor = drawerContentColor,
59 | drawerScrimColor = drawerScrimColor,
60 | backgroundColor = backgroundColor,
61 | contentColor = contentColor,
62 | content = bodyContent
63 | )
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/components/Surface.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.components
2 |
3 | import androidx.compose.foundation.BorderStroke
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.border
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.material.LocalContentColor
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.CompositionLocalProvider
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.draw.clip
12 | import androidx.compose.ui.draw.shadow
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.graphics.RectangleShape
15 | import androidx.compose.ui.graphics.Shape
16 | import androidx.compose.ui.graphics.compositeOver
17 | import androidx.compose.ui.unit.Dp
18 | import androidx.compose.ui.unit.dp
19 | import androidx.compose.ui.zIndex
20 | import com.example.play.theme.PlayTheme
21 | import kotlin.math.ln
22 |
23 | /**
24 | * An alternative to [androidx.compose.material.Surface]
25 | */
26 | @Composable
27 | fun PlaySurface(
28 | modifier: Modifier = Modifier,
29 | shape: Shape = RectangleShape,
30 | color: Color = PlayTheme.colors.uiBackground,
31 | contentColor: Color = PlayTheme.colors.textSecondary,
32 | border: BorderStroke? = null,
33 | elevation: Dp = 0.dp,
34 | content: @Composable () -> Unit
35 | ) {
36 | Box(
37 | modifier = modifier
38 | .shadow(elevation = elevation, shape = shape, clip = false)
39 | .zIndex(elevation.value)
40 | .then(if (border != null) Modifier.border(border, shape) else Modifier)
41 | .background(
42 | color = getBackgroundColorForElevation(color, elevation),
43 | shape = shape
44 | )
45 | .clip(shape)
46 | ) {
47 | CompositionLocalProvider(LocalContentColor provides contentColor, content = content)
48 | }
49 | }
50 |
51 | @Composable
52 | private fun getBackgroundColorForElevation(
53 | color: Color,
54 | elevation: Dp
55 | ): Color {
56 | return if (elevation > 0.dp
57 | ) {
58 | color.withElevation(elevation)
59 | } else {
60 | color
61 | }
62 | }
63 |
64 | /**
65 | * Applies a [Color.White] overlay to this color based on the [elevation]. This increases visibility
66 | * of elevation for surfaces in a dark theme.
67 | */
68 | private fun Color.withElevation(elevation: Dp): Color {
69 | val foreground = calculateForeground(elevation)
70 | return foreground.compositeOver(this)
71 | }
72 |
73 | /**
74 | * @return the alpha-modified [Color.White] to overlay on top of the surface color to produce
75 | * the resultant color.
76 | */
77 | private fun calculateForeground(elevation: Dp): Color {
78 | val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 20f
79 | return Color.White.copy(alpha = alpha)
80 | }
81 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/components/SwitchComponent.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.components
2 |
3 | import androidx.compose.material.Switch
4 | import androidx.compose.material.SwitchDefaults
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import com.example.play.theme.PlayTheme
8 |
9 | @Composable
10 | fun Switch(
11 | switchState: Boolean = false,
12 | updateSwitchState: (Boolean) -> Unit,
13 | modifier: Modifier
14 | ) {
15 | Switch(
16 | checked = switchState,
17 | onCheckedChange = { checked ->
18 | updateSwitchState(checked)
19 | },
20 | modifier = modifier,
21 | colors = SwitchDefaults.colors(
22 | PlayTheme.colors.switchColor
23 | )
24 | )
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/components/progressindicator/ProgressIndicator.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.components.progressindicator
2 |
3 | import androidx.compose.foundation.Canvas
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.height
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.layout.size
10 | import androidx.compose.foundation.layout.width
11 | import androidx.compose.foundation.progressSemantics
12 | import androidx.compose.material.Icon
13 | import androidx.compose.material.MaterialTheme
14 | import androidx.compose.material.icons.Icons
15 | import androidx.compose.material.icons.filled.Star
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.runtime.MutableState
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.geometry.CornerRadius
20 | import androidx.compose.ui.geometry.Size
21 | import androidx.compose.ui.graphics.Color
22 | import androidx.compose.ui.tooling.preview.Preview
23 | import androidx.compose.ui.unit.Dp
24 | import androidx.compose.ui.unit.dp
25 | import com.example.play.anim.getAppRatingBarState
26 | import com.example.play.theme.PlayTheme
27 | import com.example.play.ui.components.PlaySurface
28 |
29 | /**
30 | * A custom animated determinate linear progress indicator that represents progress by drawing a rounded rectangle.
31 | *
32 | * @param color The color of the progress indicator.
33 | * @param backgroundColor The color of the background behind the indicator, visible when the
34 | * progress has not reached that area of the overall indicator yet.
35 | */
36 | @Composable
37 | fun AnimatedProgressIndicator(
38 | modifier: Modifier = Modifier.padding(start = 8.dp, end = 8.dp),
39 | progress: Float,
40 | durationMillis: Int = 3000,
41 | color: Color = MaterialTheme.colors.primary,
42 | strokeWidth: Dp = 9.dp,
43 | backgroundColor: Color = PlayTheme.colors.progressIndicatorBg,
44 | showProgress: MutableState
45 | ) {
46 | val state = getAppRatingBarState(
47 | progress = progress, durationMillis = durationMillis, showProgress = showProgress.value
48 | )
49 | Canvas(
50 | modifier
51 | .progressSemantics(state.value)
52 | .size(280.dp, strokeWidth)
53 | ) {
54 | drawRoundRect(
55 | color = backgroundColor,
56 | cornerRadius = CornerRadius(15f, 15f),
57 | size = Size(size.width, size.height)
58 | )
59 | drawRoundRect(
60 | color = color, cornerRadius = CornerRadius(15f, 15f),
61 | size = Size(state.value * size.width, size.height)
62 | )
63 | }
64 | }
65 |
66 | @Composable
67 | fun StarRatings(
68 | modifier: Modifier = Modifier,
69 | ratings: Double = 3.5,
70 | sizeInDp: Dp = 15.dp
71 | ) {
72 | Box(modifier = modifier) {
73 | Row {
74 | for (i in 1..5) {
75 | Star(sizeInDp)
76 | }
77 | }
78 | Row {
79 | for (i in 1..ratings.toInt()) {
80 | StarFilled(sizeInDp)
81 | }
82 | }
83 | }
84 | }
85 |
86 | @Composable
87 | fun Star(sizeInDp: Dp) {
88 | Icon(
89 | imageVector = Icons.Filled.Star, tint = PlayTheme.colors.progressIndicatorBg,
90 | modifier = Modifier
91 | .height(sizeInDp)
92 | .width(sizeInDp),
93 | contentDescription = null
94 | )
95 | }
96 |
97 | @Composable
98 | fun StarFilled(sizeInDp: Dp) {
99 | Icon(
100 | imageVector = Icons.Filled.Star, tint = PlayTheme.colors.accent,
101 | modifier = Modifier
102 | .height(sizeInDp)
103 | .width(sizeInDp),
104 | contentDescription = null
105 | )
106 | }
107 |
108 | @Preview
109 | @Composable
110 | fun PreviewStarRatings() {
111 | PlayTheme {
112 | StarRatings()
113 | }
114 | }
115 |
116 | @Preview
117 | @Composable
118 | fun PreviewStar() {
119 | PlayTheme {
120 | Star(sizeInDp = 20.dp)
121 | }
122 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/details/AppDetails.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.details
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.mutableStateOf
8 | import androidx.compose.runtime.remember
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.tooling.preview.Preview
12 | import androidx.compose.ui.unit.dp
13 | import com.example.play.data.AppRepo
14 | import com.example.play.theme.PlayTheme
15 | import com.example.play.ui.components.AppBarLayout
16 | import com.example.play.ui.components.PlaySurface
17 | import com.example.play.ui.details.about.About
18 | import com.example.play.ui.details.header.Header
19 | import com.example.play.ui.details.installbutton.animated.InstallButtonLayout
20 | import com.example.play.ui.details.reviews.RatingsAndReviews
21 | import com.example.play.ui.details.screenshots.Screenshots
22 | import com.example.play.ui.details.stats.Stats
23 |
24 | @Composable
25 | fun AppDetails(
26 | appId: Long?,
27 | upPress: () -> Unit
28 | ) {
29 | val app = remember(appId) { AppRepo.getApp(appId) }
30 | val isInstalling = remember { mutableStateOf(false) }
31 |
32 | PlaySurface(
33 | modifier = Modifier.fillMaxSize()
34 | ) {
35 | AppBarLayout(upPress = upPress)
36 | LazyColumn(
37 | horizontalAlignment = Alignment.CenterHorizontally,
38 | modifier = Modifier
39 | .fillMaxSize()
40 | .padding(top = 56.dp)
41 | ) {
42 | item {
43 | app?.let { safeApp ->
44 | Header(app = safeApp, showProgress = isInstalling)
45 | Stats(app = safeApp)
46 | }
47 | InstallButtonLayout(isInstalling = isInstalling)
48 | Screenshots()
49 | About()
50 | RatingsAndReviews()
51 | }
52 | }
53 | }
54 | }
55 |
56 | @Preview("App Detail")
57 | @Composable
58 | private fun AppDetailPreview() {
59 | PlayTheme {
60 | AppDetails(
61 | appId = 1L,
62 | upPress = {}
63 | )
64 | }
65 | }
66 |
67 | //@Preview("App Detail Dark")
68 | //@Composable
69 | //private fun AppDetailDarkPreview() {
70 | // PlayTheme(darkTheme = true) {
71 | // AppDetails(
72 | // appId = 1L,
73 | // upPress = {}
74 | // )
75 | // }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/details/ScreenshotItem.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.details
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.height
9 | import androidx.compose.foundation.layout.padding
10 | import androidx.compose.foundation.layout.size
11 | import androidx.compose.foundation.layout.width
12 | import androidx.compose.material.MaterialTheme
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.tooling.preview.Preview
17 | import androidx.compose.ui.unit.dp
18 | import com.example.play.theme.PlayTheme
19 | import com.example.play.ui.components.PlayCard
20 | import com.example.play.ui.components.RoundedCornerAppImage
21 |
22 | @Composable
23 | fun AppImageItem(
24 | imageUrl: String,
25 | onImageClick: () -> Unit,
26 | modifier: Modifier = Modifier
27 | ) {
28 | PlayCard(
29 | elevation = 0.dp,
30 | shape = MaterialTheme.shapes.large,
31 | modifier = modifier
32 | .size(
33 | width = 300.dp,
34 | height = 180.dp
35 | )
36 | .padding(bottom = 8.dp)
37 | ) {
38 | Column(
39 | modifier = Modifier
40 | .clickable(onClick = onImageClick)
41 | .fillMaxSize()
42 | ) {
43 | Box(
44 | modifier = Modifier
45 | .height(180.dp)
46 | .fillMaxWidth()
47 | ) {
48 | RoundedCornerAppImage(
49 | imageUrl = imageUrl,
50 | modifier = Modifier
51 | .width(300.dp)
52 | .height(180.dp)
53 | .align(Alignment.TopStart)
54 | .padding(8.dp),
55 | cornerPercent = 5
56 | )
57 | }
58 | }
59 | }
60 | }
61 |
62 | @Preview("App Image Item")
63 | @Composable
64 | fun PlayFeaturedAppItemPreview() {
65 | PlayTheme {
66 | AppImageItem(
67 | imageUrl = "",
68 | onImageClick = { }
69 | )
70 | }
71 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/details/about/About.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.details.about
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material.Icon
7 | import androidx.compose.material.IconButton
8 | import androidx.compose.material.MaterialTheme
9 | import androidx.compose.material.Text
10 | import androidx.compose.material.icons.Icons.Outlined
11 | import androidx.compose.material.icons.outlined.ArrowForward
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.text.TextStyle
16 | import androidx.compose.ui.text.font.FontWeight
17 | import androidx.compose.ui.unit.dp
18 | import androidx.compose.ui.unit.sp
19 | import androidx.compose.ui.tooling.preview.Preview
20 | import com.example.play.theme.PlayTheme
21 | import com.example.play.ui.components.PlaySurface
22 |
23 | @Composable
24 | fun About() {
25 | Column(
26 | modifier = Modifier.padding(top = 8.dp, start = 24.dp, end = 16.dp)
27 | ) {
28 | Row(
29 | verticalAlignment = Alignment.CenterVertically,
30 | ) {
31 | Text(
32 | text = "About this app",
33 | style = TextStyle(
34 | fontWeight = FontWeight.Medium,
35 | fontSize = 16.sp,
36 | letterSpacing = 0.15.sp
37 | ),
38 | color = PlayTheme.colors.textSecondaryDark,
39 | modifier = Modifier.weight(1f)
40 | )
41 | IconButton(
42 | onClick = {},
43 | modifier = Modifier.align(Alignment.CenterVertically)
44 | ) {
45 | Icon(
46 | imageVector = Outlined.ArrowForward,
47 | tint = PlayTheme.colors.iconTint,
48 | contentDescription = null
49 | )
50 | }
51 | }
52 | Text(
53 | text = "Fun and addictive game. Start downloading and explore the new world!",
54 | style = MaterialTheme.typography.subtitle2,
55 | color = PlayTheme.colors.textSecondary
56 | )
57 | }
58 | }
59 |
60 | @Preview("About")
61 | @Composable
62 | private fun AboutPreview() {
63 | PlayTheme {
64 | PlaySurface {
65 | About()
66 | }
67 | }
68 | }
69 |
70 | @Preview("About Dark")
71 | @Composable
72 | private fun AboutDarkPreview() {
73 | PlayTheme(darkTheme = true) {
74 | PlaySurface {
75 | About()
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/details/header/Header.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.details.header
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.height
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.layout.size
10 | import androidx.compose.foundation.layout.width
11 | import androidx.compose.material.CircularProgressIndicator
12 | import androidx.compose.material.Text
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.MutableState
15 | import androidx.compose.runtime.mutableStateOf
16 | import androidx.compose.runtime.remember
17 | import androidx.compose.ui.Alignment
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.text.TextStyle
20 | import androidx.compose.ui.text.font.FontWeight
21 | import androidx.compose.ui.tooling.preview.Preview
22 | import androidx.compose.ui.unit.dp
23 | import androidx.compose.ui.unit.sp
24 | import com.example.play.anim.getAppIconSizeAnimState
25 | import com.example.play.data.App
26 | import com.example.play.data.AppRepo
27 | import com.example.play.theme.PlayTheme
28 | import com.example.play.theme.Typography
29 | import com.example.play.ui.components.PlaySurface
30 | import com.example.play.ui.components.RoundedCornerAppImage
31 |
32 | @Composable
33 | fun Header(
34 | app: App,
35 | showProgress: MutableState
36 | ) {
37 | val appIconSizeState = getAppIconSizeAnimState(showProgress = showProgress.value)
38 |
39 | Row(modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 8.dp)) {
40 | Box(
41 | modifier = Modifier
42 | .height(100.dp)
43 | .width(100.dp)
44 | ) {
45 | if (showProgress.value) {
46 | CircularProgressIndicator(
47 | color = PlayTheme.colors.accent,
48 | strokeWidth = 2.dp,
49 | modifier = Modifier.size(100.dp)
50 | )
51 | }
52 | RoundedCornerAppImage(
53 | imageUrl = app.imageUrl,
54 | modifier = Modifier
55 | .width(appIconSizeState.value)
56 | .height(appIconSizeState.value)
57 | .align(Alignment.Center)
58 | .padding(8.dp),
59 | cornerPercent = 20
60 | )
61 | }
62 | Column(
63 | modifier = Modifier.fillMaxWidth()
64 | ) {
65 | Text(
66 | text = app.name,
67 | style = TextStyle(
68 | fontWeight = FontWeight.SemiBold,
69 | fontSize = 24.sp,
70 | letterSpacing = 0.15.sp
71 | ),
72 | color = PlayTheme.colors.textPrimary,
73 | modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp)
74 | )
75 | Text(
76 | text = app.org,
77 | maxLines = 1,
78 | style = Typography.subtitle2,
79 | fontWeight = FontWeight.W600,
80 | color = PlayTheme.colors.accent,
81 | modifier = Modifier.padding(start = 16.dp, top = 4.dp)
82 | )
83 | Text(
84 | text = app.info,
85 | maxLines = 1,
86 | style = Typography.caption,
87 | color = PlayTheme.colors.iconTint,
88 | modifier = Modifier.padding(start = 16.dp)
89 | )
90 | }
91 | }
92 | }
93 |
94 | @Preview("Header")
95 | @Composable
96 | private fun HeaderPreview() {
97 | PlayTheme {
98 | PlaySurface {
99 | AppRepo.getApp(1L)?.let { safeApp ->
100 | Header(
101 | app = safeApp,
102 | showProgress = remember {
103 | mutableStateOf(true)
104 | }
105 | )
106 | }
107 | }
108 | }
109 | }
110 |
111 | @Preview("Header Dark")
112 | @Composable
113 | private fun HeaderDarkPreview() {
114 | PlayTheme(darkTheme = true) {
115 | PlaySurface {
116 | AppRepo.getApp(1L)?.let { safeApp ->
117 | Header(
118 | app = safeApp,
119 | showProgress = remember {
120 | mutableStateOf(false)
121 | }
122 | )
123 | }
124 | }
125 | }
126 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/details/installbutton/animated/AnimatedInstallButton.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.details.installbutton.animated
2 |
3 | import androidx.compose.foundation.BorderStroke
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.foundation.layout.width
9 | import androidx.compose.foundation.shape.RoundedCornerShape
10 | import androidx.compose.material.Button
11 | import androidx.compose.material.ButtonDefaults
12 | import androidx.compose.material.Text
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.MutableState
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.unit.dp
17 | import com.example.play.anim.getInstallBtnBgColorState
18 | import com.example.play.anim.getInstallBtnBorderColorState
19 | import com.example.play.anim.getInstallBtnBorderWidthState
20 | import com.example.play.anim.getInstallBtnCornerState
21 | import com.example.play.anim.getInstallButtonWidthState
22 | import com.example.play.anim.getOpenButtonGapWidthState
23 | import com.example.play.anim.getOpenButtonWidthState
24 | import com.example.play.theme.PlayTheme
25 |
26 | @Composable
27 | fun InstallButtonPanel(
28 | isPressed: MutableState
29 | ) {
30 | Row(
31 | modifier = Modifier.padding(8.dp)
32 | ) {
33 | val openButtonWidthState = getOpenButtonWidthState(isPressed = isPressed.value)
34 | val installButtonWidthState = getInstallButtonWidthState(isPressed = isPressed.value)
35 |
36 | OpenButton(
37 | isPressed = isPressed,
38 | modifier = Modifier.size(openButtonWidthState.value, 38.dp)
39 | )
40 |
41 | InstallButton(
42 | isPressed = isPressed,
43 | modifier = Modifier
44 | .size(installButtonWidthState.value, 38.dp)
45 | .weight(1f, true)
46 | )
47 | }
48 | }
49 |
50 | @Composable
51 | fun InstallButton(
52 | isPressed: MutableState,
53 | modifier: Modifier
54 | ) {
55 | val buttonBorderColorState = getInstallBtnBorderColorState(isPressed = isPressed.value)
56 | val buttonBorderWidthState = getInstallBtnBorderWidthState(isPressed = isPressed.value)
57 | val buttonBgColorState = getInstallBtnBgColorState(isPressed = isPressed.value)
58 | val buttonCornersState = getInstallBtnCornerState(isPressed = isPressed.value)
59 |
60 | Button(
61 | border = BorderStroke(
62 | buttonBorderWidthState.value,
63 | buttonBorderColorState.value
64 | ),
65 | colors = ButtonDefaults.buttonColors(
66 | backgroundColor = buttonBgColorState.value
67 | ),
68 | shape = RoundedCornerShape(buttonCornersState.value),
69 | modifier = modifier,
70 | onClick = {
71 | isPressed.value = isPressed.value.not()
72 | }
73 | ) {
74 | ButtonContent(isPressed = isPressed.value)
75 | }
76 | }
77 |
78 | @Composable
79 | fun OpenButton(
80 | isPressed: MutableState,
81 | modifier: Modifier
82 | ) {
83 | val buttonGapWidthState = getOpenButtonGapWidthState(isPressed = isPressed.value)
84 |
85 | Button(
86 | border = BorderStroke(1.dp, PlayTheme.colors.accent),
87 | colors = ButtonDefaults.buttonColors(
88 | backgroundColor = PlayTheme.colors.uiBackground
89 | ),
90 | shape = RoundedCornerShape(50),
91 | modifier = modifier,
92 | onClick = {}
93 | ) {
94 | Text(
95 | "Open",
96 | softWrap = false,
97 | color = PlayTheme.colors.accent
98 | )
99 | }
100 | Spacer(modifier = Modifier.width(buttonGapWidthState.value))
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/details/installbutton/animated/ButtonContent.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.details.installbutton.animated
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.size
7 | import androidx.compose.foundation.layout.width
8 | import androidx.compose.material.Icon
9 | import androidx.compose.material.Text
10 | import androidx.compose.material.icons.Icons
11 | import androidx.compose.material.icons.filled.CloudDownload
12 | import androidx.compose.material.icons.outlined.CloudDownload
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.getValue
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.draw.alpha
18 | import androidx.compose.ui.unit.dp
19 | import com.example.play.anim.getButtonColorState
20 | import com.example.play.anim.getButtonIconSizeState
21 | import com.example.play.anim.getInstallButtonOpacityState
22 |
23 | @Composable
24 | fun ButtonContent(
25 | isPressed: Boolean
26 | ) {
27 | val buttonTextColorState = getButtonColorState(isPressed = isPressed)
28 | val installButtonOpacityState = getInstallButtonOpacityState(isPressed = isPressed)
29 | val idleIconSizeState by getButtonIconSizeState()
30 |
31 | if (isPressed.not()) {
32 | Row(verticalAlignment = Alignment.CenterVertically) {
33 | Column(
34 | Modifier.width(24.dp),
35 | horizontalAlignment = Alignment.CenterHorizontally
36 | ) {
37 | Icon(
38 | imageVector = Icons.Outlined.CloudDownload,
39 | tint = buttonTextColorState.value,
40 | modifier = Modifier.size(idleIconSizeState.dp),
41 | contentDescription = null
42 | )
43 | }
44 | Spacer(modifier = Modifier.width(16.dp))
45 | Text(
46 | "Install",
47 | softWrap = false,
48 | color = buttonTextColorState.value
49 | )
50 | }
51 | } else {
52 | Row(verticalAlignment = Alignment.CenterVertically) {
53 | Column(
54 | Modifier.width(24.dp),
55 | horizontalAlignment = Alignment.CenterHorizontally
56 | ) {
57 | Icon(
58 | imageVector = Icons.Default.CloudDownload,
59 | tint = buttonTextColorState.value,
60 | modifier = Modifier
61 | .size(idleIconSizeState.dp)
62 | .alpha(installButtonOpacityState.value),
63 | contentDescription = null
64 | )
65 | }
66 | Spacer(modifier = Modifier.width(16.dp))
67 | Text(
68 | "Cancel",
69 | softWrap = false,
70 | color = buttonTextColorState.value
71 | )
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/details/installbutton/animated/InstallButtonLayout.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.details.installbutton.animated
2 |
3 | import androidx.compose.foundation.layout.padding
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.MutableState
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.unit.dp
10 | import androidx.compose.ui.tooling.preview.Preview
11 | import com.example.play.theme.PlayTheme
12 | import com.example.play.ui.components.PlaySurface
13 |
14 | @Composable
15 | fun InstallButtonLayout(
16 | isInstalling: MutableState
17 | ) {
18 | PlaySurface(modifier = Modifier.padding(16.dp)) {
19 | InstallButtonPanel(isPressed = isInstalling)
20 | }
21 | }
22 |
23 | @Preview("Install Button Layout")
24 | @Composable
25 | private fun AboutPreview() {
26 | PlayTheme {
27 | PlaySurface {
28 | InstallButtonLayout(isInstalling = remember {
29 | mutableStateOf(true)
30 | })
31 | }
32 | }
33 | }
34 |
35 | @Preview("Install Button Layout Dark")
36 | @Composable
37 | private fun AboutDarkPreview() {
38 | PlayTheme(darkTheme = true) {
39 | PlaySurface {
40 | InstallButtonLayout(isInstalling = remember {
41 | mutableStateOf(false)
42 | })
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/details/installbutton/plain/PlainInstallButton.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.details.installbutton.plain
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.foundation.shape.RoundedCornerShape
6 | import androidx.compose.material.Button
7 | import androidx.compose.material.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.unit.dp
11 |
12 | @Composable
13 | fun PlainInstallButton() {
14 | Button(
15 | onClick = {},
16 | shape = RoundedCornerShape(10),
17 | modifier = Modifier
18 | .fillMaxWidth()
19 | .padding(start = 32.dp, end = 32.dp, top = 32.dp, bottom = 16.dp)
20 | ) {
21 | Text("Install")
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/details/reviews/RatingsAndReviews.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.details.reviews
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.material.Icon
9 | import androidx.compose.material.IconButton
10 | import androidx.compose.material.Text
11 | import androidx.compose.material.icons.Icons.Outlined
12 | import androidx.compose.material.icons.outlined.ArrowForward
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.mutableStateOf
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.ui.Alignment
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.text.TextStyle
19 | import androidx.compose.ui.text.font.FontWeight
20 | import androidx.compose.ui.tooling.preview.Preview
21 | import androidx.compose.ui.unit.dp
22 | import androidx.compose.ui.unit.sp
23 | import com.example.play.data.AppRepo
24 | import com.example.play.theme.PlayTheme
25 | import com.example.play.theme.Typography
26 | import com.example.play.ui.components.PlaySurface
27 | import com.example.play.ui.components.progressindicator.AnimatedProgressIndicator
28 | import com.example.play.ui.components.progressindicator.StarRatings
29 |
30 | @Composable
31 | fun RatingsAndReviews() {
32 | Column(
33 | modifier = Modifier.padding(top = 4.dp, start = 24.dp, end = 16.dp, bottom = 8.dp)
34 | ) {
35 | RatingsAndReviewsHeader()
36 | Row(modifier = Modifier.padding(top = 16.dp)) {
37 | Column(modifier = Modifier.align(Alignment.CenterVertically)) {
38 | Text(
39 | text = "4.7",
40 | style = TextStyle(
41 | fontWeight = FontWeight.SemiBold,
42 | fontSize = 45.sp,
43 | letterSpacing = 1.sp
44 | ),
45 | color = PlayTheme.colors.textPrimary,
46 | modifier = Modifier.align(Alignment.Start)
47 | )
48 | StarRatings()
49 | Text(
50 | text = "2,907,517",
51 | style = TextStyle(
52 | fontWeight = FontWeight.Normal,
53 | fontSize = 11.sp,
54 | letterSpacing = 0.70.sp
55 | ),
56 | color = PlayTheme.colors.textSecondary
57 | )
58 | }
59 | AppRatingBars(
60 | modifier = Modifier
61 | .padding(start = 24.dp)
62 | .align(Alignment.CenterVertically)
63 | )
64 | }
65 | Spacer(modifier = Modifier.height(16.dp))
66 | val reviews = AppRepo.getReviews()
67 | Column {
68 | reviews.forEach {
69 | Spacer(modifier = Modifier.height(16.dp))
70 | ReviewItem(review = it)
71 | }
72 | }
73 | }
74 | }
75 |
76 | @Composable
77 | private fun RatingsAndReviewsHeader() {
78 | Row(
79 | verticalAlignment = Alignment.CenterVertically,
80 | ) {
81 | Text(
82 | text = "Ratings and reviews",
83 | style = TextStyle(
84 | fontWeight = FontWeight.Medium,
85 | fontSize = 16.sp,
86 | letterSpacing = 0.15.sp
87 | ),
88 | color = PlayTheme.colors.textSecondaryDark,
89 | modifier = Modifier.weight(1f)
90 | )
91 | IconButton(
92 | onClick = {},
93 | modifier = Modifier.align(Alignment.Top)
94 | ) {
95 | Icon(
96 | imageVector = Outlined.ArrowForward,
97 | tint = PlayTheme.colors.iconTint,
98 | contentDescription = null
99 | )
100 | }
101 | }
102 | }
103 |
104 | @Composable
105 | private fun AppRatingBars(modifier: Modifier) {
106 | val showProgress = remember { mutableStateOf(false) }
107 | Column(modifier = modifier) {
108 | Row {
109 | Text(
110 | text = "5",
111 | style = Typography.caption,
112 | color = PlayTheme.colors.textSecondaryDark,
113 | modifier = Modifier.align(Alignment.CenterVertically)
114 | )
115 | AnimatedProgressIndicator(
116 | progress = 0.8f,
117 | durationMillis = 4000,
118 | color = PlayTheme.colors.accent,
119 | showProgress = showProgress
120 | )
121 | }
122 | Spacer(modifier = Modifier.height(3.dp))
123 | Row {
124 | Text(
125 | text = "4",
126 | style = Typography.caption,
127 | color = PlayTheme.colors.textSecondaryDark,
128 | modifier = Modifier.align(Alignment.CenterVertically)
129 | )
130 | AnimatedProgressIndicator(
131 | progress = 0.5f,
132 | color = PlayTheme.colors.accent,
133 | showProgress = showProgress
134 | )
135 | }
136 | Spacer(modifier = Modifier.height(3.dp))
137 | Row {
138 | Text(
139 | text = "3",
140 | style = Typography.caption,
141 | color = PlayTheme.colors.textSecondaryDark,
142 | modifier = Modifier.align(Alignment.CenterVertically)
143 | )
144 | AnimatedProgressIndicator(
145 | progress = 0.3f,
146 | durationMillis = 4000,
147 | color = PlayTheme.colors.accent,
148 | showProgress = showProgress
149 | )
150 | }
151 | Spacer(modifier = Modifier.height(3.dp))
152 | Row {
153 | Text(
154 | text = "2",
155 | style = Typography.caption,
156 | color = PlayTheme.colors.textSecondaryDark,
157 | modifier = Modifier.align(Alignment.CenterVertically)
158 | )
159 | AnimatedProgressIndicator(
160 | progress = 0.1f,
161 | color = PlayTheme.colors.accent,
162 | showProgress = showProgress
163 | )
164 | }
165 | Spacer(modifier = Modifier.height(3.dp))
166 | Row {
167 | Text(
168 | text = "1",
169 | style = Typography.caption,
170 | color = PlayTheme.colors.textSecondaryDark,
171 | modifier = Modifier.align(Alignment.CenterVertically)
172 | )
173 | AnimatedProgressIndicator(
174 | progress = 0.2f,
175 | durationMillis = 4000,
176 | color = PlayTheme.colors.accent,
177 | showProgress = showProgress
178 | )
179 | }
180 | }
181 | showProgress.value = true
182 | }
183 |
184 | @Preview("Ratings and Reviews")
185 | @Composable
186 | private fun ReviewsPreview() {
187 | PlayTheme {
188 | PlaySurface {
189 | RatingsAndReviews()
190 | }
191 | }
192 | }
193 |
194 | @Preview("Ratings and Reviews Dark")
195 | @Composable
196 | private fun ReviewsDarkPreview() {
197 | PlayTheme(darkTheme = true) {
198 | PlaySurface {
199 | RatingsAndReviews()
200 | }
201 | }
202 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/details/reviews/ReviewItem.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.details.reviews
2 |
3 | import androidx.compose.foundation.border
4 | import androidx.compose.foundation.clickable
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.height
9 | import androidx.compose.foundation.layout.padding
10 | import androidx.compose.foundation.layout.width
11 | import androidx.compose.foundation.shape.RoundedCornerShape
12 | import androidx.compose.material.Icon
13 | import androidx.compose.material.Text
14 | import androidx.compose.material.icons.Icons
15 | import androidx.compose.material.icons.filled.MoreVert
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.ui.Alignment
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.graphics.Color
20 | import androidx.compose.ui.graphics.Shape
21 | import androidx.compose.ui.text.TextStyle
22 | import androidx.compose.ui.text.font.FontWeight
23 | import androidx.compose.ui.text.style.TextAlign.Center
24 | import androidx.compose.ui.text.style.TextOverflow.Ellipsis
25 | import androidx.compose.ui.unit.Dp
26 | import androidx.compose.ui.unit.dp
27 | import androidx.compose.ui.unit.sp
28 | import androidx.compose.ui.tooling.preview.Preview
29 | import com.example.play.data.AppRepo
30 | import com.example.play.data.Review
31 | import com.example.play.theme.PlayTheme
32 | import com.example.play.ui.components.CircularAppImage
33 | import com.example.play.ui.components.PlaySurface
34 | import com.example.play.ui.components.progressindicator.StarRatings
35 |
36 | @Composable
37 | fun ReviewItem(review: Review) {
38 | PlaySurface(
39 | modifier = Modifier.padding(bottom = 16.dp)
40 | ) {
41 | Column {
42 | Row(
43 | modifier = Modifier.padding(bottom = 8.dp)
44 | ) {
45 | CircularAppImage(
46 | imageUrl = review.userAvatarUrl,
47 | modifier = Modifier
48 | .height(35.dp)
49 | .width(35.dp)
50 | .align(Alignment.CenterVertically)
51 | .weight(2f, false)
52 | )
53 | Text(
54 | text = review.userName,
55 | style = TextStyle(
56 | fontWeight = FontWeight.Normal,
57 | fontSize = 12.sp,
58 | letterSpacing = 0.15.sp
59 | ),
60 | color = PlayTheme.colors.textPrimary,
61 | maxLines = 1,
62 | overflow = Ellipsis,
63 | modifier = Modifier
64 | .align(Alignment.CenterVertically)
65 | .weight(12f, true)
66 | .padding(start = 8.dp)
67 | )
68 | Icon(
69 | imageVector = Icons.Filled.MoreVert,
70 | contentDescription = null,
71 | modifier = Modifier
72 | .align(Alignment.CenterVertically)
73 | .weight(1f, false)
74 | .clickable(onClick = {})
75 | )
76 | }
77 | Row(
78 | modifier = Modifier.padding(bottom = 8.dp)
79 | ) {
80 | StarRatings(
81 | ratings = review.ratings, modifier = Modifier
82 | .align(Alignment.CenterVertically),
83 | sizeInDp = 10.dp
84 | )
85 | Text(
86 | text = review.date,
87 | style = TextStyle(
88 | fontWeight = FontWeight.Normal,
89 | fontSize = 12.sp,
90 | letterSpacing = 0.15.sp
91 | ),
92 | color = PlayTheme.colors.textSecondary,
93 | modifier = Modifier.padding(start = 8.dp)
94 | )
95 | }
96 | Text(
97 | text = review.reviewDesc,
98 | style = TextStyle(
99 | fontWeight = FontWeight.Normal,
100 | fontSize = 12.sp,
101 | letterSpacing = 0.15.sp
102 | ),
103 | color = PlayTheme.colors.textSecondaryDark
104 | )
105 | Row(
106 | modifier = Modifier.padding(top = 24.dp)
107 | ) {
108 | Text(
109 | text = "Was this review helpful?",
110 | style = TextStyle(
111 | fontWeight = FontWeight.Normal,
112 | fontSize = 10.sp,
113 | letterSpacing = 0.15.sp
114 | ),
115 | color = PlayTheme.colors.textSecondary,
116 | modifier = Modifier
117 | .align(Alignment.CenterVertically)
118 | .weight(3f, true)
119 | )
120 | Row(
121 | modifier = Modifier
122 | .align(Alignment.CenterVertically)
123 | .weight(1f, false)
124 | ) {
125 | Chip(text = "Yes")
126 | Spacer(modifier = Modifier.width(8.dp))
127 | Chip(text = "No")
128 | }
129 | }
130 | }
131 | }
132 | }
133 |
134 | // Chip
135 | @Composable
136 | private fun Chip(
137 | strokeWidth: Dp = 1.dp,
138 | color: Color = PlayTheme.colors.uiBorder,
139 | shape: Shape = RoundedCornerShape(50),
140 | text: String = "Yes"
141 | ) {
142 | Text(
143 | text = text,
144 | style = TextStyle(
145 | fontWeight = FontWeight.Normal,
146 | fontSize = 10.sp,
147 | textAlign = Center,
148 | letterSpacing = 0.15.sp
149 | ),
150 | color = PlayTheme.colors.textSecondary,
151 | modifier = Modifier
152 | .border(strokeWidth, color, shape)
153 | .padding(top = 2.dp, bottom = 2.dp)
154 | .width(40.dp)
155 | .height(15.dp)
156 | .clickable(onClick = {})
157 | )
158 | }
159 |
160 | @Preview("Review Item")
161 | @Composable
162 | private fun ReviewItemPreview() {
163 | PlayTheme {
164 | PlaySurface {
165 | ReviewItem(
166 | AppRepo.getReview(1L)
167 | )
168 | }
169 | }
170 | }
171 |
172 | @Preview("Review Item Dark")
173 | @Composable
174 | private fun ReviewItemDarkPreview() {
175 | PlayTheme(darkTheme = true) {
176 | PlaySurface {
177 | ReviewItem(
178 | AppRepo.getReview(1L)
179 | )
180 | }
181 | }
182 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/details/screenshots/Screenshots.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.details.screenshots
2 |
3 | import androidx.compose.foundation.layout.Spacer
4 | import androidx.compose.foundation.layout.width
5 | import androidx.compose.foundation.lazy.LazyRow
6 | import androidx.compose.foundation.lazy.items
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.tooling.preview.Preview
10 | import androidx.compose.ui.unit.dp
11 | import com.example.play.data.AppRepo
12 | import com.example.play.theme.PlayTheme
13 | import com.example.play.ui.details.AppImageItem
14 |
15 | @Composable
16 | fun Screenshots(
17 | imageUrlList: List = AppRepo.getScreenshots(),
18 | onImageClick: () -> Unit = {},
19 | modifier: Modifier = Modifier
20 | ) {
21 | //val scrollState = rememberScrollState(0)
22 | Spacer(modifier = Modifier.width(16.dp))
23 | LazyRow(
24 | modifier = modifier
25 | ) {
26 | items(imageUrlList) { imageUrlItem ->
27 | ScreenshotListItem(imageUrlItem, onImageClick)
28 | }
29 | }
30 | }
31 |
32 | @Composable
33 | fun ScreenshotListItem(
34 | imageUrl: String,
35 | onImageClick: () -> Unit
36 | ) {
37 | AppImageItem(imageUrl, onImageClick)
38 | Spacer(modifier = Modifier.width(8.dp))
39 | }
40 |
41 | @Preview("App Images")
42 | @Composable
43 | private fun AppImagesPreview() {
44 | PlayTheme {
45 | Screenshots(
46 | imageUrlList = listOf("", "", "", "")
47 | )
48 | }
49 | }
50 |
51 | @Preview("App Images Dark")
52 | @Composable
53 | private fun AppImagesDarkPreview() {
54 | PlayTheme(darkTheme = true) {
55 | Screenshots(
56 | imageUrlList = listOf("", "", "", "")
57 | )
58 | }
59 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/details/stats/Stats.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.details.stats
2 |
3 | import androidx.compose.foundation.background
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.fillMaxHeight
8 | import androidx.compose.foundation.layout.height
9 | import androidx.compose.foundation.layout.padding
10 | import androidx.compose.foundation.layout.width
11 | import androidx.compose.material.Icon
12 | import androidx.compose.material.MaterialTheme
13 | import androidx.compose.material.Text
14 | import androidx.compose.material.icons.Icons
15 | import androidx.compose.material.icons.outlined.Download
16 | import androidx.compose.material.icons.outlined.Star
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.ui.Alignment
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.text.font.FontWeight
21 | import androidx.compose.ui.text.style.TextOverflow.Ellipsis
22 | import androidx.compose.ui.tooling.preview.Preview
23 | import androidx.compose.ui.unit.dp
24 | import com.example.play.data.App
25 | import com.example.play.data.AppRepo
26 | import com.example.play.theme.PlayTheme
27 | import com.example.play.ui.components.PlaySurface
28 |
29 | @Composable
30 | fun Stats(app: App) {
31 | Row(
32 | modifier = Modifier
33 | .padding(top = 8.dp, start = 16.dp, end = 16.dp)
34 | ) {
35 | Column(modifier = Modifier.padding(end = 12.dp)) {
36 | Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
37 | Text(
38 | text = app.ratings,
39 | maxLines = 1,
40 | overflow = Ellipsis,
41 | style = MaterialTheme.typography.subtitle1,
42 | fontWeight = FontWeight.Bold,
43 | color = PlayTheme.colors.textSecondaryDark
44 | )
45 | Icon(
46 | imageVector = Icons.Outlined.Star,
47 | tint = PlayTheme.colors.textSecondaryDark,
48 | modifier = Modifier
49 | .padding(end = 8.dp)
50 | .width(14.dp)
51 | .height(14.dp)
52 | .align(Alignment.CenterVertically),
53 | contentDescription = null
54 | )
55 | }
56 | Text(
57 | text = "636K Reviews",
58 | maxLines = 1,
59 | style = MaterialTheme.typography.caption,
60 | color = PlayTheme.colors.textSecondary
61 | )
62 | }
63 | Spacer(
64 | modifier = Modifier
65 | .width(1.dp)
66 | .height(24.dp)
67 | .fillMaxHeight()
68 | .align(Alignment.CenterVertically)
69 | .background(color = PlayTheme.colors.uiBorder)
70 | )
71 | Column(modifier = Modifier.padding(start = 12.dp, end = 12.dp)) {
72 | Icon(
73 | imageVector = Icons.Outlined.Download,
74 | tint = PlayTheme.colors.textSecondaryDark,
75 | modifier = Modifier
76 | .padding(end = 8.dp)
77 | .width(21.dp)
78 | .height(21.dp)
79 | .align(Alignment.CenterHorizontally),
80 | contentDescription = null
81 | )
82 | Text(
83 | text = "9.9 MB",
84 | maxLines = 1,
85 | style = MaterialTheme.typography.caption,
86 | color = PlayTheme.colors.textSecondary,
87 | modifier = Modifier.align(Alignment.CenterHorizontally)
88 | )
89 | }
90 | Spacer(
91 | modifier = Modifier
92 | .width(1.dp)
93 | .height(24.dp)
94 | .fillMaxHeight()
95 | .align(Alignment.CenterVertically)
96 | .background(color = PlayTheme.colors.uiBorder)
97 | )
98 | Column(modifier = Modifier.padding(start = 12.dp, end = 12.dp)) {
99 | Text(
100 | text = "3+",
101 | maxLines = 1,
102 | style = MaterialTheme.typography.subtitle1,
103 | fontWeight = FontWeight.Bold,
104 | color = PlayTheme.colors.textSecondaryDark,
105 | modifier = Modifier.align(Alignment.CenterHorizontally)
106 | )
107 | Text(
108 | text = "Rated For 3+",
109 | maxLines = 1,
110 | style = MaterialTheme.typography.caption,
111 | color = PlayTheme.colors.textSecondary,
112 | modifier = Modifier.align(Alignment.CenterHorizontally)
113 | )
114 | }
115 | Spacer(
116 | modifier = Modifier
117 | .width(1.dp)
118 | .height(24.dp)
119 | .fillMaxHeight()
120 | .align(Alignment.CenterVertically)
121 | .background(color = PlayTheme.colors.uiBorder)
122 | )
123 | Column(modifier = Modifier.padding(start = 12.dp, end = 12.dp)) {
124 | Text(
125 | text = "10M+",
126 | maxLines = 1,
127 | style = MaterialTheme.typography.subtitle1,
128 | fontWeight = FontWeight.Bold,
129 | color = PlayTheme.colors.textSecondaryDark,
130 | modifier = Modifier.align(Alignment.CenterHorizontally)
131 | )
132 | Text(
133 | text = "Downloads",
134 | maxLines = 1,
135 | style = MaterialTheme.typography.caption,
136 | color = PlayTheme.colors.textSecondary,
137 | modifier = Modifier.align(Alignment.CenterHorizontally)
138 | )
139 | }
140 | }
141 | }
142 |
143 | @Preview("Stats")
144 | @Composable
145 | private fun StatsPreview() {
146 | PlayTheme {
147 | PlaySurface {
148 | AppRepo.getApp(1L)?.let { safeApp ->
149 | Stats(safeApp)
150 | }
151 | }
152 | }
153 | }
154 |
155 | @Preview("Stats Dark")
156 | @Composable
157 | private fun StatsDarkPreview() {
158 | PlayTheme(darkTheme = true) {
159 | PlaySurface {
160 | AppRepo.getApp(1L)?.let { safeApp ->
161 | Stats(safeApp)
162 | }
163 | }
164 | }
165 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/games/Games.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.games
2 |
3 | import androidx.compose.animation.Crossfade
4 | import androidx.compose.animation.core.LinearOutSlowInEasing
5 | import androidx.compose.animation.core.TweenSpec
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.mutableStateOf
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.runtime.saveable.rememberSaveable
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.tooling.preview.Preview
14 | import com.example.play.data.AppRepo
15 | import com.example.play.theme.PlayTheme
16 | import com.example.play.ui.AppsCategory
17 | import com.example.play.ui.AppsCategory.Categories
18 | import com.example.play.ui.AppsCategory.EarlyAccess
19 | import com.example.play.ui.AppsCategory.EditorsChoice
20 | import com.example.play.ui.AppsCategory.ForYou
21 | import com.example.play.ui.AppsCategory.TopCharts
22 | import com.example.play.ui.apps.applist.ForYouLayout
23 | import com.example.play.ui.apps.applist.TopChartsLayout
24 | import com.example.play.ui.components.PlaySurface
25 | import com.example.play.ui.AppsCategoryTabs
26 | import com.google.accompanist.insets.navigationBarsPadding
27 |
28 | @Composable
29 | fun Games(
30 | onAppSelected: (Long) -> Unit,
31 | modifier: Modifier = Modifier,
32 | appsCategories: List = getGamesCategoriesList()
33 | ) {
34 | val forYouData = remember { AppRepo.getForYouGames() }
35 | val topChartsData = remember { AppRepo.getTopChartsGames() }
36 | val (currentCategory, setCurrentCategory) = rememberSaveable { mutableStateOf(ForYou) }
37 |
38 | PlaySurface(modifier = modifier.fillMaxSize()) {
39 | Column(modifier = Modifier.navigationBarsPadding(left = true, right = true)) {
40 | AppsCategoryTabs(
41 | categories = appsCategories,
42 | selectedCategory = currentCategory,
43 | onCategorySelected = setCurrentCategory
44 | )
45 | val tweenSpec = remember { getAnimSpec() }
46 |
47 | Crossfade(currentCategory, animationSpec = tweenSpec) { category ->
48 | when (category) {
49 | ForYou -> ForYouLayout(forYouData, onAppSelected = onAppSelected)
50 | TopCharts -> TopChartsLayout(topChartsData, onAppSelected = onAppSelected)
51 | else -> ForYouLayout(forYouData, onAppSelected = onAppSelected)
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
58 | private fun getAnimSpec(): TweenSpec {
59 | return TweenSpec(
60 | durationMillis = 600,
61 | easing = LinearOutSlowInEasing
62 | )
63 | }
64 |
65 | private fun getGamesCategoriesList() = listOf(
66 | ForYou, TopCharts, Categories, EditorsChoice, EarlyAccess
67 | )
68 |
69 | @Preview("Games")
70 | @Composable
71 | fun GamesPreview() {
72 | PlayTheme {
73 | Games(onAppSelected = {})
74 | }
75 | }
76 |
77 | @Preview("Games • Dark Theme")
78 | @Composable
79 | fun GamesDarkPreview() {
80 | PlayTheme(darkTheme = true) {
81 | Games(onAppSelected = {})
82 | }
83 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/movies/Movies.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.movies
2 |
3 | import androidx.compose.animation.Crossfade
4 | import androidx.compose.animation.core.LinearOutSlowInEasing
5 | import androidx.compose.animation.core.TweenSpec
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.mutableStateOf
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.runtime.saveable.rememberSaveable
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.tooling.preview.Preview
14 | import com.example.play.data.AppRepo
15 | import com.example.play.theme.PlayTheme
16 | import com.example.play.ui.MoviesCategory
17 | import com.example.play.ui.MoviesCategory.ForYou
18 | import com.example.play.ui.MoviesCategory.NewReleases
19 | import com.example.play.ui.MoviesCategory.TopSelling
20 | import com.example.play.ui.components.PlaySurface
21 | import com.example.play.ui.MoviesCategoryTabs
22 | import com.example.play.ui.movies.movielist.MoviesForYouLayout
23 | import com.example.play.ui.movies.movielist.TopSellingLayout
24 | import com.google.accompanist.insets.navigationBarsPadding
25 |
26 | @Composable
27 | fun Movies(
28 | modifier: Modifier = Modifier,
29 | moviesCategories: List = listOf(
30 | ForYou, TopSelling, NewReleases
31 | )
32 | ) {
33 | val forYouData = remember { AppRepo.getForYouMovies() }
34 | val topSellingData = remember { AppRepo.getTopSellingMovies() }
35 | val newReleasesData = remember { AppRepo.getNewReleasesMovies() }
36 |
37 | val (currentCategory, setCurrentCategory) = rememberSaveable {
38 | mutableStateOf(ForYou)
39 | }
40 |
41 | PlaySurface(modifier = modifier.fillMaxSize()) {
42 | Column(modifier = Modifier.navigationBarsPadding(left = true, right = true)) {
43 | MoviesCategoryTabs(
44 | categories = moviesCategories,
45 | selectedCategory = currentCategory,
46 | onCategorySelected = setCurrentCategory
47 | )
48 | val tweenSpec = remember { getAnimSpec() }
49 | Crossfade(currentCategory, animationSpec = tweenSpec) { category ->
50 | when (category) {
51 | ForYou -> MoviesForYouLayout(forYouData)
52 | TopSelling -> TopSellingLayout(topSellingData)
53 | NewReleases -> TopSellingLayout(newReleasesData)
54 | }
55 | }
56 | }
57 | }
58 | }
59 |
60 | private fun getAnimSpec(): TweenSpec {
61 | return TweenSpec(
62 | durationMillis = 600,
63 | easing = LinearOutSlowInEasing
64 | )
65 | }
66 |
67 | @Preview("Movies")
68 | @Composable
69 | fun MoviesPreview() {
70 | PlayTheme {
71 | Movies()
72 | }
73 | }
74 |
75 | @Preview("Movies • Dark Theme")
76 | @Composable
77 | fun MoviesDarkPreview() {
78 | PlayTheme(darkTheme = true) {
79 | Movies()
80 | }
81 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/movies/movielist/MovieItem.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.movies.movielist
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Box
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.material.Icon
15 | import androidx.compose.material.MaterialTheme
16 | import androidx.compose.material.Text
17 | import androidx.compose.material.icons.Icons
18 | import androidx.compose.material.icons.outlined.Star
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.ui.Alignment
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.text.TextStyle
23 | import androidx.compose.ui.text.font.FontWeight
24 | import androidx.compose.ui.text.style.TextAlign.Center
25 | import androidx.compose.ui.text.style.TextOverflow.Ellipsis
26 | import androidx.compose.ui.tooling.preview.Preview
27 | import androidx.compose.ui.unit.dp
28 | import androidx.compose.ui.unit.sp
29 | import com.example.play.data.AppRepo
30 | import com.example.play.data.Movie
31 | import com.example.play.theme.PlayTheme
32 | import com.example.play.ui.components.PlayCard
33 | import com.example.play.ui.components.PlaySurface
34 | import com.example.play.ui.components.RoundedCornerAppImage
35 |
36 | @Composable
37 | fun MovieItem(
38 | movie: Movie,
39 | modifier: Modifier = Modifier
40 | ) {
41 | PlayCard(
42 | elevation = 0.dp,
43 | shape = MaterialTheme.shapes.large,
44 | modifier = modifier
45 | .size(
46 | width = 120.dp,
47 | height = 240.dp
48 | )
49 | .padding(bottom = 8.dp)
50 | ) {
51 | Column(
52 | modifier = Modifier
53 | .clickable(onClick = {})
54 | .fillMaxSize()
55 | ) {
56 | Box(
57 | modifier = Modifier
58 | .height(180.dp)
59 | .fillMaxWidth()
60 | ) {
61 | RoundedCornerAppImage(
62 | imageUrl = movie.imageUrl,
63 | modifier = Modifier
64 | .width(120.dp)
65 | .height(180.dp)
66 | .align(Alignment.TopStart)
67 | .padding(8.dp),
68 | cornerPercent = 10
69 | )
70 | }
71 | Spacer(modifier = Modifier.height(3.dp))
72 | Text(
73 | text = movie.name,
74 | style = TextStyle(
75 | fontWeight = FontWeight.Normal,
76 | fontSize = 12.sp,
77 | letterSpacing = 0.15.sp
78 | ),
79 | color = PlayTheme.colors.textPrimary,
80 | maxLines = 1,
81 | overflow = Ellipsis,
82 | modifier = Modifier.padding(start = 8.dp)
83 | )
84 | Spacer(modifier = Modifier.height(4.dp))
85 | Row(modifier = Modifier.padding(start = 8.dp)) {
86 | Text(
87 | text = movie.ratings,
88 | style = TextStyle(
89 | fontWeight = FontWeight.Light,
90 | fontSize = 12.sp,
91 | letterSpacing = 0.15.sp
92 | ),
93 | color = PlayTheme.colors.textSecondaryDark,
94 | maxLines = 1,
95 | overflow = Ellipsis
96 | )
97 | Icon(
98 | imageVector = Icons.Outlined.Star,
99 | tint = PlayTheme.colors.iconTint,
100 | modifier = Modifier
101 | .padding(start = 2.dp, end = 8.dp)
102 | .width(8.dp)
103 | .height(8.dp)
104 | .align(Alignment.CenterVertically),
105 | contentDescription = null
106 | )
107 | Text(
108 | text = movie.price,
109 | style = TextStyle(
110 | fontWeight = FontWeight.Light,
111 | fontSize = 12.sp,
112 | letterSpacing = 0.15.sp
113 | ),
114 | color = PlayTheme.colors.textSecondaryDark,
115 | maxLines = 1,
116 | overflow = Ellipsis
117 | )
118 | }
119 | }
120 | }
121 | }
122 |
123 | @Composable
124 | fun TopSellingMovieItem(
125 | movie: Movie,
126 | modifier: Modifier = Modifier
127 | ) {
128 | PlaySurface(
129 | modifier = modifier
130 | .fillMaxWidth()
131 | .clickable(onClick = { })
132 | ) {
133 | Row(modifier = Modifier.padding(start = 16.dp, end = 8.dp, top = 4.dp, bottom = 4.dp)) {
134 | Text(
135 | text = movie.id.toString(),
136 | style = TextStyle(
137 | fontWeight = FontWeight.Normal,
138 | fontSize = 14.sp
139 | ),
140 | textAlign = Center,
141 | color = PlayTheme.colors.textSecondary,
142 | modifier = Modifier
143 | .align(Alignment.CenterVertically)
144 | .width(30.dp)
145 | .padding(end = 8.dp)
146 | )
147 | Box(
148 | modifier = Modifier
149 | .height(90.dp)
150 | .width(65.dp)
151 | ) {
152 | RoundedCornerAppImage(
153 | imageUrl = movie.imageUrl,
154 | modifier = Modifier
155 | .fillMaxWidth()
156 | .align(Alignment.TopStart)
157 | .padding(8.dp),
158 | cornerPercent = 5
159 | )
160 | }
161 | Column(
162 | modifier = Modifier
163 | .padding(top = 4.dp, start = 8.dp)
164 | .align(Alignment.Top)
165 | ) {
166 | Text(
167 | text = movie.name,
168 | style = TextStyle(
169 | fontWeight = FontWeight.Normal,
170 | fontSize = 14.sp,
171 | letterSpacing = 0.15.sp
172 | ),
173 | color = PlayTheme.colors.textPrimary,
174 | maxLines = 1,
175 | modifier = Modifier.padding(bottom = 3.dp),
176 | overflow = Ellipsis
177 | )
178 | Text(
179 | text = movie.category,
180 | style = TextStyle(
181 | fontWeight = FontWeight.Normal,
182 | fontSize = 12.sp,
183 | letterSpacing = 0.15.sp
184 | ),
185 | color = PlayTheme.colors.textSecondary,
186 | maxLines = 1,
187 | overflow = Ellipsis,
188 | modifier = Modifier.padding(bottom = 2.dp)
189 | )
190 | Row(modifier = Modifier.padding(bottom = 2.dp)) {
191 | Text(
192 | text = movie.ratings,
193 | style = TextStyle(
194 | fontWeight = FontWeight.Light,
195 | fontSize = 12.sp,
196 | letterSpacing = 0.15.sp
197 | ),
198 | color = PlayTheme.colors.textSecondaryDark,
199 | maxLines = 1,
200 | overflow = Ellipsis
201 | )
202 | Icon(
203 | imageVector = Icons.Outlined.Star,
204 | tint = PlayTheme.colors.iconTint,
205 | modifier = Modifier
206 | .padding(start = 2.dp, end = 8.dp)
207 | .width(8.dp)
208 | .height(8.dp)
209 | .align(Alignment.CenterVertically),
210 | contentDescription = null
211 | )
212 | }
213 | Text(
214 | text = movie.price,
215 | style = TextStyle(
216 | fontWeight = FontWeight.Light,
217 | fontSize = 12.sp,
218 | letterSpacing = 0.15.sp
219 | ),
220 | color = PlayTheme.colors.textSecondaryDark,
221 | maxLines = 1,
222 | overflow = Ellipsis,
223 | modifier = Modifier.padding(bottom = 2.dp),
224 | )
225 | }
226 | }
227 | }
228 | }
229 |
230 | @Preview("Movie Item")
231 | @Composable
232 | fun PlayMovieItemPreview() {
233 | PlayTheme {
234 | val movie = AppRepo.getMovie(2L)
235 | MovieItem(
236 | movie = movie
237 | )
238 | }
239 | }
240 |
241 | @Preview("Top Selling Movie Item")
242 | @Composable
243 | fun TopSellingMovieItemPreview() {
244 | PlayTheme {
245 | val movie = AppRepo.getMovie(1L)
246 | TopSellingMovieItem(
247 | movie = movie
248 | )
249 | }
250 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/movies/movielist/MoviesForYou.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.movies.movielist
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.layout.width
9 | import androidx.compose.foundation.lazy.LazyColumn
10 | import androidx.compose.foundation.lazy.LazyRow
11 | import androidx.compose.foundation.lazy.items
12 | import androidx.compose.material.Icon
13 | import androidx.compose.material.IconButton
14 | import androidx.compose.material.Text
15 | import androidx.compose.material.icons.Icons
16 | import androidx.compose.material.icons.outlined.ArrowForward
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.runtime.key
19 | import androidx.compose.ui.Alignment
20 | import androidx.compose.ui.Modifier
21 | import androidx.compose.ui.text.TextStyle
22 | import androidx.compose.ui.text.font.FontWeight
23 | import androidx.compose.ui.text.style.TextOverflow
24 | import androidx.compose.ui.tooling.preview.Preview
25 | import androidx.compose.ui.unit.dp
26 | import androidx.compose.ui.unit.sp
27 | import com.example.play.data.AppRepo
28 | import com.example.play.data.Movie
29 | import com.example.play.data.MovieCollection
30 | import com.example.play.theme.PlayTheme
31 | import com.google.accompanist.insets.navigationBarsPadding
32 |
33 | @Composable
34 | fun MoviesForYouLayout(
35 | data: List,
36 | modifier: Modifier = Modifier
37 | ) {
38 | Spacer(
39 | modifier = Modifier
40 | .height(4.dp)
41 | )
42 | LazyColumn(modifier = modifier) {
43 | items(data) { moviesCollection ->
44 | key(moviesCollection.id) {
45 | MoviesForYou(
46 | moviesCollection = moviesCollection
47 | )
48 | }
49 | }
50 | }
51 | Spacer(
52 | modifier = Modifier
53 | .navigationBarsPadding(left = false, right = false)
54 | .height(8.dp)
55 | )
56 | }
57 |
58 | @Composable
59 | fun MoviesForYou(
60 | moviesCollection: MovieCollection,
61 | modifier: Modifier = Modifier
62 | ) {
63 | Column(modifier = modifier) {
64 | Row(
65 | verticalAlignment = Alignment.CenterVertically,
66 | modifier = Modifier
67 | .padding(start = 24.dp)
68 | ) {
69 | Text(
70 | text = moviesCollection.name,
71 | style = TextStyle(
72 | fontWeight = FontWeight.Medium,
73 | fontSize = 16.sp,
74 | letterSpacing = 0.15.sp
75 | ),
76 | color = PlayTheme.colors.textPrimary,
77 | maxLines = 1,
78 | overflow = TextOverflow.Ellipsis,
79 | modifier = Modifier.weight(1f)
80 | )
81 | IconButton(
82 | onClick = {},
83 | modifier = Modifier.align(Alignment.CenterVertically)
84 | ) {
85 | Icon(
86 | imageVector = Icons.Outlined.ArrowForward,
87 | tint = PlayTheme.colors.iconTint,
88 | contentDescription = null
89 | )
90 | }
91 | }
92 | MoviesList(moviesCollection.movies)
93 | }
94 | }
95 |
96 | @Composable
97 | private fun MoviesList(
98 | movies: List
99 | ) {
100 | LazyRow(modifier = Modifier.padding(start = 16.dp)) {
101 | items(movies) { movie ->
102 | MovieItem(movie)
103 | Spacer(modifier = Modifier.width(1.dp))
104 | }
105 | }
106 | }
107 |
108 | @Preview("Movies For You list preview")
109 | @Composable
110 | fun MoviesForYouListPreview() {
111 | PlayTheme {
112 | MoviesForYouLayout(data = AppRepo.getMovies())
113 | }
114 | }
115 |
116 | @Preview("Movies For You list dark preview")
117 | @Composable
118 | fun MoviesForYouListDarkPreview() {
119 | PlayTheme(darkTheme = true) {
120 | MoviesForYouLayout(data = AppRepo.getMovies())
121 | }
122 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/ui/movies/movielist/TopSelling.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.ui.movies.movielist
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.foundation.lazy.items
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.unit.dp
10 | import androidx.compose.ui.tooling.preview.Preview
11 | import com.example.play.data.AppRepo
12 | import com.example.play.data.Movie
13 | import com.example.play.theme.PlayTheme
14 |
15 | @Composable
16 | fun TopSellingLayout(
17 | moviesCollection: List,
18 | modifier: Modifier = Modifier
19 | ) {
20 | Column(modifier = modifier) {
21 | TopSellingMoviesList(
22 | movies = moviesCollection
23 | )
24 | }
25 | }
26 |
27 | @Composable
28 | private fun TopSellingMoviesList(
29 | movies: List,
30 | modifier: Modifier = Modifier
31 | ) {
32 | LazyColumn(modifier = modifier.padding(top = 8.dp)) {
33 | items(movies) { movie ->
34 | TopSellingMovieItem(movie)
35 | }
36 | }
37 | }
38 |
39 | @Preview("Top Selling preview")
40 | @Composable
41 | fun TopSellingPreview() {
42 | PlayTheme {
43 | TopSellingLayout(moviesCollection = AppRepo.getTopSellingMovies())
44 | }
45 | }
46 |
47 | @Preview("Top Selling dark preview")
48 | @Composable
49 | fun TopSellingDarkPreview() {
50 | PlayTheme(darkTheme = true) {
51 | TopSellingLayout(moviesCollection = AppRepo.getTopSellingMovies())
52 | }
53 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/utils/Navigation.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.play.utils
18 |
19 | import android.os.Parcelable
20 | import androidx.activity.OnBackPressedCallback
21 | import androidx.activity.OnBackPressedDispatcher
22 | import androidx.compose.runtime.saveable.listSaver
23 | import androidx.compose.runtime.toMutableStateList
24 |
25 | /**
26 | * A simple navigator which maintains a back stack.
27 | */
28 | class Navigator private constructor(
29 | initialBackStack: List,
30 | backDispatcher: OnBackPressedDispatcher
31 | ) {
32 | constructor(
33 | initial: T,
34 | backDispatcher: OnBackPressedDispatcher
35 | ) : this(listOf(initial), backDispatcher)
36 |
37 | private val backStack = initialBackStack.toMutableStateList()
38 | private val backCallback = object : OnBackPressedCallback(canGoBack()) {
39 | override fun handleOnBackPressed() {
40 | back()
41 | }
42 | }.also { callback ->
43 | backDispatcher.addCallback(callback)
44 | }
45 | val current: T get() = backStack.last()
46 |
47 | fun back() {
48 | backStack.removeAt(backStack.lastIndex)
49 | backCallback.isEnabled = canGoBack()
50 | }
51 |
52 | fun navigate(destination: T) {
53 | backStack += destination
54 | backCallback.isEnabled = canGoBack()
55 | }
56 |
57 | private fun canGoBack(): Boolean = backStack.size > 1
58 |
59 | companion object {
60 | /**
61 | * Serialize the back stack to save to instance state.
62 | */
63 | fun saver(backDispatcher: OnBackPressedDispatcher) =
64 | listSaver, T>(
65 | save = { navigator -> navigator.backStack.toList() },
66 | restore = { backstack -> Navigator(backstack, backDispatcher) }
67 | )
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/play/utils/SystemUi.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.play.utils
18 |
19 | /**
20 | * Moved to https://google.github.io/accompanist/systemuicontroller/
21 | */
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_black_rect.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_apps.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_books.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_books_otlined.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_box.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_download.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_games.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_movies.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_movies_outlined.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star_solid.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/user_profile_pic.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/app/src/main/res/drawable/user_profile_pic.jpg
--------------------------------------------------------------------------------
/app/src/main/res/font/product_sans_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/app/src/main/res/font/product_sans_bold.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/product_sans_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/app/src/main/res/font/product_sans_regular.ttf
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | JetStore
3 |
4 |
5 | Games
6 | Apps
7 | Movies
8 | Books
9 |
10 |
11 | Search App
12 | No matches for “%1s”
13 | Try broadening your search
14 | %1d items
15 |
16 | For You
17 | Top Charts
18 | Categories
19 | Editor\'s Choice
20 | Early Access
21 |
22 | Top Selling
23 | New Releases
24 | Filters
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | import com.example.play.buildsrc.Libs
4 |
5 | buildscript {
6 | repositories {
7 | google()
8 | mavenCentral()
9 | }
10 | dependencies {
11 | classpath Libs.androidGradlePlugin
12 | classpath Libs.Kotlin.gradlePlugin
13 |
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | subprojects {
20 | repositories {
21 | google()
22 | mavenCentral()
23 |
24 | if (!Libs.AndroidX.Compose.snapshot.isEmpty()) {
25 | maven {
26 | url "https://androidx.dev/snapshots/builds/${Libs.AndroidX.Compose.snapshot}/artifacts/ui/repository/"
27 | }
28 | }
29 |
30 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
31 | }
32 |
33 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
34 | kotlinOptions {
35 | jvmTarget = '1.8'
36 | allWarningsAsErrors = true
37 | // Opt-in to experimental compose APIs
38 | freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn'
39 | }
40 | }
41 | }
42 |
43 | task clean(type: Delete) {
44 | delete rootProject.buildDir
45 | }
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | repositories {
2 | jcenter()
3 | }
4 |
5 | plugins {
6 | `kotlin-dsl`
7 | }
--------------------------------------------------------------------------------
/buildSrc/build/classes/kotlin/main/META-INF/buildSrc.kotlin_module:
--------------------------------------------------------------------------------
1 | " *
--------------------------------------------------------------------------------
/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$Accompanist.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$Accompanist.class
--------------------------------------------------------------------------------
/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$AndroidX$Compose.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$AndroidX$Compose.class
--------------------------------------------------------------------------------
/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$AndroidX$Test$Ext.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$AndroidX$Test$Ext.class
--------------------------------------------------------------------------------
/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$AndroidX$Test.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$AndroidX$Test.class
--------------------------------------------------------------------------------
/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$AndroidX.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$AndroidX.class
--------------------------------------------------------------------------------
/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$Coroutines.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$Coroutines.class
--------------------------------------------------------------------------------
/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$Kotlin.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$Kotlin.class
--------------------------------------------------------------------------------
/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs.class
--------------------------------------------------------------------------------
/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Versions.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Versions.class
--------------------------------------------------------------------------------
/buildSrc/build/kotlin/buildSrcjar-classes.txt:
--------------------------------------------------------------------------------
1 | /Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$Accompanist.class:/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$AndroidX$Activity.class:/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$AndroidX$Compose.class:/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$AndroidX$Lifecycle.class:/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$AndroidX$Test$Ext.class:/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$AndroidX$Test.class:/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$AndroidX.class:/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$Coroutines.class:/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$Kotlin.class:/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs$LottieView.class:/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Libs.class:/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/kotlin/main/com/example/play/buildsrc/Versions.class
--------------------------------------------------------------------------------
/buildSrc/build/libs/buildSrc.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/buildSrc/build/libs/buildSrc.jar
--------------------------------------------------------------------------------
/buildSrc/build/pluginUnderTestMetadata/plugin-under-test-metadata.properties:
--------------------------------------------------------------------------------
1 | implementation-classpath=/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/java/main\:/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/groovy/main\:/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/classes/kotlin/main\:/Users/pushpalroy/AndroidStudioProjects/Play/buildSrc/build/resources/main
2 |
--------------------------------------------------------------------------------
/buildSrc/build/reports/plugin-development/validation-report.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/buildSrc/build/reports/plugin-development/validation-report.txt
--------------------------------------------------------------------------------
/buildSrc/build/tmp/jar/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 |
3 |
--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/example/play/buildsrc/Dependencies.kt:
--------------------------------------------------------------------------------
1 | package com.example.play.buildsrc
2 |
3 | object Versions {
4 | const val ktlint = "0.41.0"
5 | }
6 |
7 | object Libs {
8 | private const val agpVersion = "7.0.0-beta03"
9 | const val androidGradlePlugin = "com.android.tools.build:gradle:$agpVersion"
10 | const val junit = "junit:junit:4.13"
11 |
12 | object Accompanist {
13 | const val version = "0.10.0"
14 | const val coil = "com.google.accompanist:accompanist-coil:$version"
15 | const val insets = "com.google.accompanist:accompanist-insets:$version"
16 | const val systemuicontroller = "com.google.accompanist:accompanist-systemuicontroller:$version"
17 | }
18 |
19 | object Kotlin {
20 | const val version = "1.4.32"
21 | const val stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$version"
22 | const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$version"
23 | const val extensions = "org.jetbrains.kotlin:kotlin-android-extensions:$version"
24 | }
25 |
26 | object Coroutines {
27 | private const val version = "1.4.1"
28 | const val core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version"
29 | const val android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version"
30 | const val test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$version"
31 | }
32 |
33 | object LottieView {
34 | private const val lottieVersion = "3.7.0"
35 | private const val lottieComposeVersion = "1.0.0-beta07-1"
36 | const val lottie = "com.airbnb.android:lottie:${lottieVersion}"
37 | const val lottieCompose = "com.airbnb.android:lottie-compose:${lottieComposeVersion}"
38 | }
39 |
40 | object AndroidX {
41 | private const val appCompatVer = "1.3.0"
42 | private const val coreKtxVer = "1.6.0-alpha03"
43 | private const val navComposeVer = "2.4.0-alpha01"
44 | const val appcompat = "androidx.appcompat:$appCompatVer"
45 | const val coreKtx = "androidx.core:core-ktx:$coreKtxVer"
46 | const val navigation = "androidx.navigation:navigation-compose:$navComposeVer"
47 |
48 | object Activity {
49 | private const val activityComposeVer = "1.3.0-alpha08"
50 | const val activityCompose = "androidx.activity:activity-compose:$activityComposeVer"
51 | }
52 |
53 | object Compose {
54 | const val snapshot = ""
55 | const val composeVer = "1.0.0-beta07"
56 |
57 | const val runtime = "androidx.compose.runtime:runtime:$composeVer"
58 | const val foundation = "androidx.compose.foundation:foundation:${composeVer}"
59 | const val layout = "androidx.compose.foundation:foundation-layout:${composeVer}"
60 | const val ui = "androidx.compose.ui:ui:${composeVer}"
61 | const val uiUtil = "androidx.compose.ui:ui-util:${composeVer}"
62 | const val material = "androidx.compose.material:material:${composeVer}"
63 | const val animation = "androidx.compose.animation:animation:${composeVer}"
64 | const val tooling = "androidx.compose.ui:ui-tooling:${composeVer}"
65 | const val iconsExtended = "androidx.compose.material:material-icons-extended:$composeVer"
66 |
67 | // Test rules and transitive dependencies
68 | const val uiTestJunit = "androidx.compose.ui:ui-test-junit4:$composeVer"
69 |
70 | // Needed for createComposeRule, but not createAndroidComposeRule
71 | const val uiTestManifest = "androidx.compose.ui:ui-test-manifest:$composeVer"
72 | }
73 |
74 | object Lifecycle {
75 | private const val vmComposeVer = "1.0.0-alpha05"
76 | const val viewModelCompose = "androidx.lifecycle:lifecycle-viewmodel-compose:$vmComposeVer"
77 | }
78 |
79 | object Test {
80 | private const val version = "1.3.0"
81 | const val core = "androidx.test:core:$version"
82 | const val rules = "androidx.test:rules:$version"
83 |
84 | object Ext {
85 | private const val version = "1.1.2-rc01"
86 | const val junit = "androidx.test.ext:junit-ktx:$version"
87 | }
88 |
89 | const val espressoCore = "androidx.test.espresso:espresso-core:3.2.0"
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 |
8 | # Specifies the JVM arguments used for the daemon process.
9 | # The setting is particularly useful for tweaking memory settings.
10 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
11 |
12 | org.gradle.configureondemand=true
13 | org.gradle.caching=true
14 | org.gradle.parallel=true
15 |
16 | # AndroidX package structure to make it clearer which packages are bundled with the
17 | # Android operating system, and which are packaged with your app"s APK
18 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
19 | android.useAndroidX=true
20 |
21 | # Automatically convert third-party libraries to use AndroidX
22 | android.enableJetifier=true
23 |
24 | # Kotlin code style for this project: "official" or "obsolete":
25 | kotlin.code.style=official
26 |
27 | # Enable R8 full mode.
28 | android.enableR8.fullMode=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Feb 12 03:27:51 IST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/screenshots/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/screenshots/demo.gif
--------------------------------------------------------------------------------
/screenshots/screenshot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/screenshots/screenshot_1.png
--------------------------------------------------------------------------------
/screenshots/screenshot_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/screenshots/screenshot_2.png
--------------------------------------------------------------------------------
/screenshots/screenshot_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/screenshots/screenshot_3.png
--------------------------------------------------------------------------------
/screenshots/screenshot_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/screenshots/screenshot_4.png
--------------------------------------------------------------------------------
/screenshots/screenshot_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/screenshots/screenshot_5.png
--------------------------------------------------------------------------------
/screenshots/screenshot_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pushpalroy/jetstore/5d9487920c60a3a582f99bdcfa3a8a71dd174d46/screenshots/screenshot_6.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = "Play"
2 | include ':app'
3 |
--------------------------------------------------------------------------------