Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature coin list #15

Merged
merged 78 commits into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
2d0249c
feature_coin_list module initial commit
RyanKoech Jan 6, 2023
597dfe1
set up dependencies
RyanKoech Jan 7, 2023
5996104
implement api service
RyanKoech Jan 7, 2023
3b060ec
implement api service di module
RyanKoech Jan 7, 2023
34b34a6
implement repository
RyanKoech Jan 7, 2023
8c2c2b9
defined entity
RyanKoech Jan 7, 2023
54d9c5b
implement getCoins usecase
RyanKoech Jan 7, 2023
fb9d0c6
fix data types
RyanKoech Jan 7, 2023
c3f1481
implement repository fake
RyanKoech Jan 7, 2023
33d4299
add test dependencies
RyanKoech Jan 7, 2023
3df3e9b
implement tests
RyanKoech Jan 7, 2023
9b099e9
correct build import
RyanKoech Jan 7, 2023
bb5330e
fix circular dependency
RyanKoech Jan 7, 2023
a6cd0f8
implement searchbar component
RyanKoech Jan 7, 2023
853583d
add basic screens
RyanKoech Jan 7, 2023
7076ea9
remove unused imports
RyanKoech Jan 7, 2023
e1e54c3
update coin entity structure
RyanKoech Jan 8, 2023
e447aa7
implemented market cap string util
RyanKoech Jan 8, 2023
eb83079
update string format
RyanKoech Jan 8, 2023
73deff9
implement search tag
RyanKoech Jan 8, 2023
0a3f45f
add enum class
RyanKoech Jan 8, 2023
b3dd350
add color resources
RyanKoech Jan 8, 2023
0dfd218
add arrow icon resource
RyanKoech Jan 8, 2023
728bdda
implement coin item component
RyanKoech Jan 8, 2023
744e642
implement repo di module
RyanKoech Jan 8, 2023
9964a46
implement sorting logic
RyanKoech Jan 8, 2023
2561678
implement viewmodel
RyanKoech Jan 8, 2023
eaffc06
add screen state logic
RyanKoech Jan 8, 2023
5bd21ec
change function visibility and val name
RyanKoech Jan 8, 2023
e6b69b4
update logic to use state and state callback
RyanKoech Jan 8, 2023
770d773
implement search tag display and parameters
RyanKoech Jan 8, 2023
35d3f48
implement sorting info state management
RyanKoech Jan 8, 2023
26d41fd
implement coin list logic
RyanKoech Jan 8, 2023
079a918
fix sorting logic
RyanKoech Jan 8, 2023
13203e8
implement basic cache
RyanKoech Jan 8, 2023
b00cbac
refactor total volume data type
RyanKoech Jan 8, 2023
a609208
implemented coin search logic in vm and usecase
RyanKoech Jan 8, 2023
0568dce
updated search bar logic to seach coins
RyanKoech Jan 8, 2023
37cbb00
implement loading component
RyanKoech Jan 10, 2023
152f4f1
fix not caching bug
RyanKoech Jan 10, 2023
9022002
update work flow of screenstate
RyanKoech Jan 10, 2023
3191bd4
fix component bug and update
RyanKoech Jan 10, 2023
0ad6ff0
add gif resources
RyanKoech Jan 10, 2023
539b218
implement no coin found screen
RyanKoech Jan 10, 2023
cc1cb59
update to make more flexible
RyanKoech Jan 10, 2023
17255cf
refactored to utli error component
RyanKoech Jan 10, 2023
34fce7e
implement error screen
RyanKoech Jan 10, 2023
e91d289
add logic to display fallback error screens
RyanKoech Jan 10, 2023
163cf52
add room dependencies
RyanKoech Jan 12, 2023
0fc8d26
add local coin dto
RyanKoech Jan 12, 2023
5631043
implement database and dao
RyanKoech Jan 12, 2023
3903930
implement local coin databse di mobule
RyanKoech Jan 12, 2023
399bca8
implement type converter
RyanKoech Jan 12, 2023
91135c4
implement repositories to interact with dao
RyanKoech Jan 12, 2023
16dc0a6
implement usecase to fetch local coins
RyanKoech Jan 12, 2023
e0c5096
implement logic to save remote coins to db
RyanKoech Jan 12, 2023
af34946
implement local fallback when remote coin fetching fails
RyanKoech Jan 12, 2023
088bb18
clear cache after period
RyanKoech Jan 12, 2023
c652736
implement pull to refresh
RyanKoech Jan 12, 2023
093b1f1
fix indentation
RyanKoech Jan 12, 2023
171653f
add coinItemCallbackOnClick callback
RyanKoech Jan 12, 2023
576eab6
add logic to get coin from db
RyanKoech Jan 12, 2023
65351e7
update name to use real repository
RyanKoech Jan 12, 2023
a5adad3
update theme font size
RyanKoech Jan 12, 2023
20309dc
refactor varaible into companion object
RyanKoech Jan 12, 2023
53f9e0b
cleaned up code
RyanKoech Jan 12, 2023
0b11c56
moved logic to handle remote failure to usecase
RyanKoech Jan 12, 2023
c59f4a0
implement tests
RyanKoech Jan 12, 2023
7491ba6
refactored usecase deletion
RyanKoech Jan 12, 2023
cef4cc2
android test truth dependency
RyanKoech Jan 12, 2023
dae494c
implement dao tests
RyanKoech Jan 12, 2023
2fa1963
add test tags
RyanKoech Jan 13, 2023
812accd
update dependencies
RyanKoech Jan 13, 2023
b7b4275
implement tests
RyanKoech Jan 13, 2023
55c7628
add test tags
RyanKoech Jan 13, 2023
0126a0a
implement tests
RyanKoech Jan 13, 2023
8015d7b
implement sorting tests
RyanKoech Jan 13, 2023
78bbe90
added internet permission
RyanKoech Jan 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ buildscript {
mockk_version = '1.13.3'
coroutines_test_version = '1.6.4'
turbine_version = '0.12.1'
okhttp_logging_interceptor_version = '5.0.0-alpha.9'
retrofit_version = '2.9.0'
}
dependencies {
classpath "com.google.dagger:hilt-android-gradle-plugin:2.42"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import com.ryankoech.krypto.common.presentation.components.KryptoButton
@Composable
fun ErrorScreen(
modifier : Modifier = Modifier,
onButtonClick : () -> Unit,
onButtonClick : () -> Unit = {},
messageText : String,
buttonText : String,
buttonText : String = "Try Again",
showButton : Boolean = true,
@DrawableRes res : Int,
) {

Expand All @@ -27,8 +28,9 @@ fun ErrorScreen(
horizontalAlignment = Alignment.CenterHorizontally
) {

Box(modifier = Modifier.size(112.dp)) {
Box(modifier = Modifier.size(80.dp)) {
GifImage(
modifier = Modifier.fillMaxSize(),
res = res
)
}
Expand All @@ -40,12 +42,14 @@ fun ErrorScreen(
style = MaterialTheme.typography.body1
)

Spacer(modifier = Modifier.height(24.dp))
if(showButton){
Spacer(modifier = Modifier.height(24.dp))

KryptoButton(
text = buttonText,
onClick = onButtonClick
)
KryptoButton(
text = buttonText,
onClick = onButtonClick
)
}

}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.ryankoech.krypto.common.presentation.components

import androidx.compose.foundation.border
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.ryankoech.krypto.common.R
import com.ryankoech.krypto.common.presentation.theme.Green200
import com.ryankoech.krypto.common.presentation.theme.KryptoTheme

@Composable
fun SearchBar(
value : String,
onValueChange : (String) -> Unit,
placeholder: String,
modifier: Modifier = Modifier
) {

TextField(
modifier = modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
.border(width = 1.dp, shape = MaterialTheme.shapes.small, color = Color.Black),
shape = MaterialTheme.shapes.small,
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.icon_search),
contentDescription = "Search icon"
)
},
placeholder = {
Text(
text = placeholder,
style = MaterialTheme.typography.caption,
color = Color.Black
)
},
textStyle = MaterialTheme.typography.caption,
value = value,
onValueChange = onValueChange,
singleLine = true,
colors = TextFieldDefaults.textFieldColors(
focusedIndicatorColor = Color.Unspecified,
unfocusedIndicatorColor = Color.Unspecified,
cursorColor = Green200,
backgroundColor = Color.White
),
)


}

@Preview
@Composable
fun SearchBarPreview() {
KryptoTheme {
Surface(
modifier = Modifier.fillMaxWidth()
) {


SearchBar(
value = "",
onValueChange = {},
placeholder = "Discover asset, coin or token"
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ val Typography = Typography(
caption = TextStyle(
fontFamily = CircularStd,
fontWeight = FontWeight.Medium,
fontSize = 12.sp,
fontSize = 14.sp,
),
button = TextStyle(
fontFamily = CircularStd,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,49 @@ fun getChangeColor(change : Float) : Color {
}

fun getInTwoDecimalPlaces(value : Double) : String{
val df = DecimalFormat("#.##")
val df = DecimalFormat("#,##0.00")
return df.format(value)
}
fun getFormattedMarketCap(context: Context, value : Long) : String {
val million = 1_000_000
val billion = 1_000_000_000
val trillion = 1_000_000_000_000
val quadrillion = 1_000_000_000_000_000
val quintillion = 1_000_000_000_000_000_000

return if(value < million) {
val df = DecimalFormat("#,###.##")
context.getString(R.string.coin_market_cap, df.format(value), "")
}else if(value < billion){
context.getString(
R.string.coin_market_cap,
getInFourDecimalPlaces(value.toDouble() / million.toDouble()),
"M"
)
}else if(value < trillion){
context.getString(
R.string.coin_market_cap,
getInFourDecimalPlaces(value.toDouble() / billion.toDouble()),
"Bn"
)
}else if(value < quadrillion){
context.getString(
R.string.coin_market_cap,
getInFourDecimalPlaces(value.toDouble() / trillion.toDouble()),
"Tr"
)
}else if(value < quintillion){
context.getString(
R.string.coin_market_cap,
getInFourDecimalPlaces(value.toDouble() / quadrillion.toDouble()),
"Tr"
)
}else {
"Over $1 Qnt"
}
}

fun getInFourDecimalPlaces(value : Double) : String{
val df = DecimalFormat("#,##0.0000")
return df.format(value)
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added common/src/main/res/drawable/icon_gif_search.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions common/src/main/res/drawable/icon_search.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector android:height="24dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#00000000"
android:pathData="M11,11m-5.5,0a5.5,5.5 0,1 1,11 0a5.5,5.5 0,1 1,-11 0"
android:strokeColor="#000000" android:strokeWidth="1.44"/>
<path android:fillColor="#00000000" android:pathData="M15,15L19,19"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="1.44"/>
</vector>
1 change: 1 addition & 0 deletions common/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
<string name="coin_image_content_description">%s Image</string>
<string name="coin_value_change">%s%s%%</string>
<string name="coin_amount_balance">%s%s</string>
<string name="coin_market_cap">MCap $%s %s</string>
</resources>
1 change: 1 addition & 0 deletions feature_coin_list/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
115 changes: 115 additions & 0 deletions feature_coin_list/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'dagger.hilt.android.plugin'
id 'kotlin-kapt'
}

android {
namespace 'com.ryankoech.krypto.feature_coin_list'
compileSdk 33

defaultConfig {
minSdk 21
targetSdk 33

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
}
packagingOptions {
resources.excludes.add("META-INF/*")
}
}

dependencies {

// core android
implementation "androidx.core:core-ktx:$ktx_core_version"
implementation "androidx.appcompat:appcompat:$app_compat_version"
implementation "com.google.android.material:material:$material_version"

// local modules
implementation project(":common")

// dagger-hilt - for dependency injection
implementation "com.google.dagger:hilt-android:$hilt_android_version"
implementation "androidx.hilt:hilt-navigation-compose:$hilt_navigation_compose_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_android_compile_version"
kapt "androidx.hilt:hilt-compiler:$hilt_compile_version"

// retrofit - for network call
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"

// okhttp logging interceptor
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_logging_interceptor_version"

// timber
implementation "com.jakewharton.timber:timber:$timber_version"

//Compose
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation "androidx.activity:activity-compose:$compose_activity_version"

// room - for storing data locally
implementation "androidx.room:room-ktx:$room_version"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
kapt "androidx.room:room-compiler:$room_version"

// junit4
testImplementation "junit:junit:$junit_version"

// google truth
testImplementation "com.google.truth:truth:$google_truth_version"

// mockk
testImplementation "io.mockk:mockk-android:$mockk_version"
testImplementation "io.mockk:mockk-agent:$mockk_version"

// coroutines test
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_test_version"

// turbine
testImplementation "app.cash.turbine:turbine:$turbine_version"

// junit4
androidTestImplementation "androidx.test.ext:junit:$junit_ext_version"

// espresso
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"

// compose ui
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"

// google truth
androidTestImplementation "com.google.truth:truth:$google_truth_version"

// mockk
androidTestImplementation "io.mockk:mockk-android:$mockk_version"
androidTestImplementation "io.mockk:mockk-agent:$mockk_version"
}
Empty file.
21 changes: 21 additions & 0 deletions feature_coin_list/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.ryankoech.krypto.feature_coin_list

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.ryankoech.krypto.feature_coin_list.test", appContext.packageName)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.ryankoech.krypto.feature_coin_list.core.util

class Util {

companion object {
const val MOCK_EXCEPTION_MESSAGE = "mock exception message"
}

}
Loading