diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 4e3844e..a2d7c21 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,5 +1,6 @@ + diff --git a/app/build.gradle b/app/build.gradle index 1bb6d8d..58bda0d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,6 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' - id 'kotlin-kapt' - id 'dagger.hilt.android.plugin' } android { @@ -10,7 +8,7 @@ android { defaultConfig { applicationId "com.plcoding.weatherapp" - minSdk 21 + minSdk 26 targetSdk 32 versionCode 1 versionName "1.0" @@ -62,13 +60,12 @@ dependencies { androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' + implementation 'androidx.compose.material3:material3:1.0.0-alpha01' - //Dagger - Hilt - implementation "com.google.dagger:hilt-android:2.40.5" - kapt "com.google.dagger:hilt-android-compiler:2.40.5" - implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03" - kapt "androidx.hilt:hilt-compiler:1.0.0" - implementation 'androidx.hilt:hilt-navigation-compose:1.0.0' + // Koin + implementation "io.insert-koin:koin-android:3.2.0-beta-1" + implementation "io.insert-koin:koin-androidx-navigation:3.2.0-beta-1" + implementation "io.insert-koin:koin-androidx-compose:3.2.0-beta-1" // Location Services implementation 'com.google.android.gms:play-services-location:20.0.0' diff --git a/app/src/main/java/com/plcoding/weatherapp/WeatherApp.kt b/app/src/main/java/com/plcoding/weatherapp/WeatherApp.kt index 0def967..a776db1 100644 --- a/app/src/main/java/com/plcoding/weatherapp/WeatherApp.kt +++ b/app/src/main/java/com/plcoding/weatherapp/WeatherApp.kt @@ -1,7 +1,16 @@ package com.plcoding.weatherapp import android.app.Application -import dagger.hilt.android.HiltAndroidApp +import com.plcoding.weatherapp.di.appModule +import org.koin.android.ext.koin.androidContext +import org.koin.core.context.startKoin -@HiltAndroidApp -class WeatherApp: Application() \ No newline at end of file +class WeatherApp: Application(){ + override fun onCreate() { + super.onCreate() + startKoin { + androidContext(this@WeatherApp) + modules(appModule) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/data/location/DefaultLocationTracker.kt b/app/src/main/java/com/plcoding/weatherapp/data/location/DefaultLocationTracker.kt index a53d06b..75abc30 100644 --- a/app/src/main/java/com/plcoding/weatherapp/data/location/DefaultLocationTracker.kt +++ b/app/src/main/java/com/plcoding/weatherapp/data/location/DefaultLocationTracker.kt @@ -11,11 +11,10 @@ import com.google.android.gms.location.FusedLocationProviderClient import com.plcoding.weatherapp.domain.location.LocationTracker import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.suspendCancellableCoroutine -import javax.inject.Inject import kotlin.coroutines.resume @ExperimentalCoroutinesApi -class DefaultLocationTracker @Inject constructor( +class DefaultLocationTracker ( private val locationClient: FusedLocationProviderClient, private val application: Application ): LocationTracker { diff --git a/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherApi.kt b/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherApi.kt index 67ac577..90d790b 100644 --- a/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherApi.kt +++ b/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherApi.kt @@ -1,5 +1,7 @@ package com.plcoding.weatherapp.data.remote +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.http.GET import retrofit2.http.Query @@ -10,4 +12,15 @@ interface WeatherApi { @Query("latitude") lat: Double, @Query("longitude") long: Double ): WeatherDto + + companion object{ + private const val BASE_URL = "https://api.open-meteo.com/" + fun create() : WeatherApi{ + return Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(MoshiConverterFactory.create()) + .build() + .create(WeatherApi::class.java) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/data/repository/WeatherRepositoryImpl.kt b/app/src/main/java/com/plcoding/weatherapp/data/repository/WeatherRepositoryImpl.kt index 49879dd..4ba11ef 100644 --- a/app/src/main/java/com/plcoding/weatherapp/data/repository/WeatherRepositoryImpl.kt +++ b/app/src/main/java/com/plcoding/weatherapp/data/repository/WeatherRepositoryImpl.kt @@ -5,9 +5,8 @@ import com.plcoding.weatherapp.data.remote.WeatherApi import com.plcoding.weatherapp.domain.repository.WeatherRepository import com.plcoding.weatherapp.domain.util.Resource import com.plcoding.weatherapp.domain.weather.WeatherInfo -import javax.inject.Inject -class WeatherRepositoryImpl @Inject constructor( +class WeatherRepositoryImpl ( private val api: WeatherApi ): WeatherRepository { diff --git a/app/src/main/java/com/plcoding/weatherapp/di/AppModule.kt b/app/src/main/java/com/plcoding/weatherapp/di/AppModule.kt index 6662377..e484fb6 100644 --- a/app/src/main/java/com/plcoding/weatherapp/di/AppModule.kt +++ b/app/src/main/java/com/plcoding/weatherapp/di/AppModule.kt @@ -1,35 +1,37 @@ package com.plcoding.weatherapp.di -import android.app.Application import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationServices +import com.plcoding.weatherapp.data.location.DefaultLocationTracker import com.plcoding.weatherapp.data.remote.WeatherApi -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory -import retrofit2.create -import javax.inject.Singleton +import com.plcoding.weatherapp.data.repository.WeatherRepositoryImpl +import com.plcoding.weatherapp.domain.location.LocationTracker +import com.plcoding.weatherapp.domain.repository.WeatherRepository +import com.plcoding.weatherapp.presentation.WeatherViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.koin.android.ext.koin.androidApplication +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module -@Module -@InstallIn(SingletonComponent::class) -object AppModule { +@OptIn(ExperimentalCoroutinesApi::class) +val appModule = module { + single { + WeatherApi.create() + } + + single { + LocationServices.getFusedLocationProviderClient(androidContext()) + } - @Provides - @Singleton - fun provideWeatherApi(): WeatherApi { - return Retrofit.Builder() - .baseUrl("https://api.open-meteo.com/") - .addConverterFactory(MoshiConverterFactory.create()) - .build() - .create() + single { + DefaultLocationTracker(get(), androidApplication()) } - @Provides - @Singleton - fun provideFusedLocationProviderClient(app: Application): FusedLocationProviderClient { - return LocationServices.getFusedLocationProviderClient(app) + single { + WeatherRepositoryImpl(get()) + } + viewModel { + WeatherViewModel(get(), get()) } } \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/di/LocationModule.kt b/app/src/main/java/com/plcoding/weatherapp/di/LocationModule.kt deleted file mode 100644 index 77a203a..0000000 --- a/app/src/main/java/com/plcoding/weatherapp/di/LocationModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.plcoding.weatherapp.di - -import com.plcoding.weatherapp.data.location.DefaultLocationTracker -import com.plcoding.weatherapp.domain.location.LocationTracker -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import kotlinx.coroutines.ExperimentalCoroutinesApi -import javax.inject.Singleton - -@ExperimentalCoroutinesApi -@Module -@InstallIn(SingletonComponent::class) -abstract class LocationModule { - - @Binds - @Singleton - abstract fun bindLocationTracker(defaultLocationTracker: DefaultLocationTracker): LocationTracker -} \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/di/RepositoryModule.kt b/app/src/main/java/com/plcoding/weatherapp/di/RepositoryModule.kt deleted file mode 100644 index 2dcbff7..0000000 --- a/app/src/main/java/com/plcoding/weatherapp/di/RepositoryModule.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.plcoding.weatherapp.di - -import com.plcoding.weatherapp.data.location.DefaultLocationTracker -import com.plcoding.weatherapp.data.repository.WeatherRepositoryImpl -import com.plcoding.weatherapp.domain.location.LocationTracker -import com.plcoding.weatherapp.domain.repository.WeatherRepository -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import kotlinx.coroutines.ExperimentalCoroutinesApi -import javax.inject.Singleton - -@ExperimentalCoroutinesApi -@Module -@InstallIn(SingletonComponent::class) -abstract class RepositoryModule { - - @Binds - @Singleton - abstract fun bindWeatherRepository( - weatherRepositoryImpl: WeatherRepositoryImpl - ): WeatherRepository -} \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/HourlyWeatherDisplay.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/HourlyWeatherDisplay.kt index 4e2cd57..1de8096 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/HourlyWeatherDisplay.kt +++ b/app/src/main/java/com/plcoding/weatherapp/presentation/HourlyWeatherDisplay.kt @@ -4,7 +4,8 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.width -import androidx.compose.material.Text +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -20,7 +21,7 @@ import java.time.format.DateTimeFormatter fun HourlyWeatherDisplay( weatherData: WeatherData, modifier: Modifier = Modifier, - textColor: Color = Color.White + textColor: Color = MaterialTheme.colorScheme.onBackground ) { val formattedTime = remember(weatherData) { weatherData.time.format( @@ -34,7 +35,7 @@ fun HourlyWeatherDisplay( ) { Text( text = formattedTime, - color = Color.LightGray + color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.86f) ) Image( painter = painterResource(id = weatherData.weatherType.iconRes), diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/MainActivity.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/MainActivity.kt index 3445490..f6fd73e 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/MainActivity.kt +++ b/app/src/main/java/com/plcoding/weatherapp/presentation/MainActivity.kt @@ -2,29 +2,28 @@ package com.plcoding.weatherapp.presentation import android.Manifest import android.os.Bundle +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.ProgressBar import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts -import androidx.activity.viewModels -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.Text +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.plcoding.weatherapp.presentation.ui.theme.DarkBlue -import com.plcoding.weatherapp.presentation.ui.theme.DeepBlue +import androidx.compose.ui.viewinterop.AndroidView import com.plcoding.weatherapp.presentation.ui.theme.WeatherAppTheme -import dagger.hilt.android.AndroidEntryPoint +import org.koin.androidx.viewmodel.ext.android.viewModel -@AndroidEntryPoint class MainActivity : ComponentActivity() { - private val viewModel: WeatherViewModel by viewModels() + private val viewModel by viewModel() private lateinit var permissionLauncher: ActivityResultLauncher> override fun onCreate(savedInstanceState: Bundle?) { @@ -34,39 +33,61 @@ class MainActivity : ComponentActivity() { ) { viewModel.loadWeatherInfo() } - permissionLauncher.launch(arrayOf( - Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.ACCESS_COARSE_LOCATION, - )) + permissionLauncher.launch( + arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION, + ) + ) setContent { WeatherAppTheme { - Box( - modifier = Modifier.fillMaxSize() + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.primaryContainer ) { - Column( - modifier = Modifier - .fillMaxSize() - .background(DarkBlue) + Box( + modifier = Modifier.fillMaxSize() ) { - WeatherCard( - state = viewModel.state, - backgroundColor = DeepBlue - ) - Spacer(modifier = Modifier.height(16.dp)) - WeatherForecast(state = viewModel.state) - } - if(viewModel.state.isLoading) { - CircularProgressIndicator( - modifier = Modifier.align(Alignment.Center) - ) - } - viewModel.state.error?.let { error -> - Text( - text = error, - color = Color.Red, - textAlign = TextAlign.Center, - modifier = Modifier.align(Alignment.Center) - ) + Column( + modifier = Modifier + .fillMaxSize() + ) { + WeatherCard( + state = viewModel.state, + backgroundColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.25f), + modifier = Modifier.padding(16.dp) + ) + Spacer(modifier = Modifier.height(16.dp)) + WeatherForecast( + state = viewModel.state, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) + } + if (viewModel.state.isLoading) { + AndroidView( + factory = { c -> + val progressBar = ProgressBar(c).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + progressBar + }, + modifier = Modifier + .align(Alignment.Center) + ) + } + viewModel.state.error?.let { error -> + Text( + text = error, + color = MaterialTheme.colorScheme.onError, + textAlign = TextAlign.Center, + modifier = Modifier.align(Alignment.Center) + ) + } } } } diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherCard.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherCard.kt index 1940d36..fcc37c1 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherCard.kt +++ b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherCard.kt @@ -3,8 +3,9 @@ package com.plcoding.weatherapp.presentation import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Card -import androidx.compose.material.Text +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -26,10 +27,10 @@ fun WeatherCard( modifier: Modifier = Modifier ) { state.weatherInfo?.currentWeatherData?.let { data -> - Card( - backgroundColor = backgroundColor, + Surface( + color = backgroundColor, shape = RoundedCornerShape(10.dp), - modifier = modifier.padding(16.dp) + modifier = modifier ) { Column( modifier = Modifier @@ -44,7 +45,7 @@ fun WeatherCard( ) }", modifier = Modifier.align(Alignment.End), - color = Color.White + color = MaterialTheme.colorScheme.onBackground ) Spacer(modifier = Modifier.height(16.dp)) Image( @@ -56,13 +57,13 @@ fun WeatherCard( Text( text = "${data.temperatureCelsius}°C", fontSize = 50.sp, - color = Color.White + color = MaterialTheme.colorScheme.onBackground ) Spacer(modifier = Modifier.height(16.dp)) Text( text = data.weatherType.weatherDesc, fontSize = 20.sp, - color = Color.White + color = MaterialTheme.colorScheme.onBackground ) Spacer(modifier = Modifier.height(32.dp)) Row( @@ -73,22 +74,22 @@ fun WeatherCard( value = data.pressure.roundToInt(), unit = "hpa", icon = ImageVector.vectorResource(id = R.drawable.ic_pressure), - iconTint = Color.White, - textStyle = TextStyle(color = Color.White) + iconTint = MaterialTheme.colorScheme.onBackground, + textStyle = TextStyle(color = MaterialTheme.colorScheme.onBackground) ) WeatherDataDisplay( value = data.humidity.roundToInt(), unit = "%", icon = ImageVector.vectorResource(id = R.drawable.ic_drop), - iconTint = Color.White, - textStyle = TextStyle(color = Color.White) + iconTint = MaterialTheme.colorScheme.onBackground, + textStyle = TextStyle(color = MaterialTheme.colorScheme.onBackground) ) WeatherDataDisplay( value = data.windSpeed.roundToInt(), unit = "km/h", icon = ImageVector.vectorResource(id = R.drawable.ic_wind), - iconTint = Color.White, - textStyle = TextStyle(color = Color.White) + iconTint = MaterialTheme.colorScheme.onBackground, + textStyle = TextStyle(color = MaterialTheme.colorScheme.onBackground) ) } } diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherDataDisplay.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherDataDisplay.kt index fc3077a..594ee11 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherDataDisplay.kt +++ b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherDataDisplay.kt @@ -4,14 +4,13 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.material.Icon -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherForecast.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherForecast.kt index a75d391..b4ea9da 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherForecast.kt +++ b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherForecast.kt @@ -1,12 +1,14 @@ package com.plcoding.weatherapp.presentation +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.material.Text +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -16,27 +18,30 @@ fun WeatherForecast( modifier: Modifier = Modifier ) { state.weatherInfo?.weatherDataPerDay?.get(0)?.let { data -> - Column( - modifier = modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) + Surface( + modifier = modifier, + color = MaterialTheme.colorScheme.primary.copy(alpha = 0.67f), + shape = RoundedCornerShape(10.dp), + shadowElevation = 0.dp ) { - Text( - text = "Today", - fontSize = 20.sp, - color = Color.White - ) - Spacer(modifier = Modifier.height(16.dp)) - LazyRow(content = { - items(data) { weatherData -> - HourlyWeatherDisplay( - weatherData = weatherData, - modifier = Modifier - .height(100.dp) - .padding(horizontal = 16.dp) - ) - } - }) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp) + ) { + Text( + text = "Today", + fontSize = 20.sp, + color = MaterialTheme.colorScheme.onPrimary + ) + Spacer(modifier = Modifier.height(16.dp)) + WeatherGraph( + data = data, + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(state = rememberScrollState()) + ) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherGraph.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherGraph.kt new file mode 100644 index 0000000..7dfbb33 --- /dev/null +++ b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherGraph.kt @@ -0,0 +1,154 @@ +package com.plcoding.weatherapp.presentation + +import android.graphics.Paint +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp +import com.plcoding.weatherapp.domain.weather.WeatherData +import com.plcoding.weatherapp.presentation.ui.theme.CoolColor +import com.plcoding.weatherapp.presentation.ui.theme.HotColor +import kotlin.math.abs + +@Composable +fun WeatherGraph( + modifier: Modifier = Modifier, + data: List +) { + val arcColor = MaterialTheme.colorScheme.tertiary + val circleColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.6f) + val max = data.maxOf { weatherData -> + weatherData.temperatureCelsius + }.toFloat() + val min = data.minOf { it.temperatureCelsius }.toFloat() + val diff = max - min + 1 + Row( + modifier = modifier, + ) { + Column { + val width = with(LocalDensity.current) { + 120.dp.toPx() + } + val depth = with(LocalDensity.current) { + 8.dp.toPx() + } + BoxWithConstraints( + modifier = Modifier + .padding(vertical = 16.dp) + .height(120.dp) + .width(120.dp * data.size) + ) { + val height = constraints.maxHeight + Canvas( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth() + ) { + drawPath( + getMainGraphCurve( + data, + width, height, + ), + color = arcColor, + style = Stroke(width = 8f) + ) + data.forEachIndexed { index, weatherData -> + val x = index * (width) + width / 2f + val y = + height * (max - weatherData.temperatureCelsius.toFloat()) / diff - depth * 2 + val yCenter = height * (max - weatherData.temperatureCelsius.toFloat()) / diff + drawCircle( + color = circleColor, + radius = depth / 2f, + center = Offset(x, yCenter) + ) + if (weatherData.temperatureCelsius.toFloat() == max) { + drawContext.canvas.nativeCanvas.apply { + drawText("${weatherData.temperatureCelsius}°C", + x, y, + Paint().apply { + textSize = 50F + color = HotColor.hashCode() + textAlign = Paint.Align.CENTER + } + ) + } + } + if (weatherData.temperatureCelsius.toFloat() == min) { + drawContext.canvas.nativeCanvas.apply { + drawText("${weatherData.temperatureCelsius}°C", + x, y, + Paint().apply { + textSize = 50F + color = CoolColor.hashCode() + textAlign = Paint.Align.CENTER + } + ) + } + } + } + } + } + Spacer(modifier = Modifier.height(16.dp)) + Row( + modifier = Modifier + .width(120.dp * data.size) + .height(120.dp) + ) { + data.forEach { weatherData -> + HourlyWeatherDisplay( + weatherData = weatherData, + modifier = Modifier + .height(100.dp) + .width(120.dp) + .padding(horizontal = 16.dp) + ) + } + } + } + } +} + +fun Path.standardQuadFromTo(from: Offset, to: Offset) { + quadraticBezierTo( + from.x, + from.y, + abs(from.x + to.x) / 2f, + abs(from.y + to.y) / 2f + ) +} + +fun getMainGraphCurve(data: List, width: Float, height: Int): Path { + val path = Path() + val max = data.maxOf { weatherData -> + weatherData.temperatureCelsius + }.toFloat() + val min = data.minOf { it.temperatureCelsius }.toFloat() + val diff = max - min + 1 + var prevPoint = Offset(width / 2f, (max - data[0].temperatureCelsius.toFloat()) / diff) + data.forEachIndexed { index, weatherData -> + val x = index * (width) + width / 2f + val y = height * (max - weatherData.temperatureCelsius.toFloat()) / diff + val nextPoint = Offset(x, y) + when (index) { + 0 -> { + path.moveTo(x, y) + } + data.size - 1 -> { + path.lineTo(nextPoint.x, nextPoint.y) + } + else -> { + path.standardQuadFromTo(prevPoint, nextPoint) + } + } + prevPoint = nextPoint + } + return path +} \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherViewModel.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherViewModel.kt index e35faee..b770196 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherViewModel.kt +++ b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherViewModel.kt @@ -8,12 +8,9 @@ import androidx.lifecycle.viewModelScope import com.plcoding.weatherapp.domain.location.LocationTracker import com.plcoding.weatherapp.domain.repository.WeatherRepository import com.plcoding.weatherapp.domain.util.Resource -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import javax.inject.Inject -@HiltViewModel -class WeatherViewModel @Inject constructor( +class WeatherViewModel ( private val repository: WeatherRepository, private val locationTracker: LocationTracker ): ViewModel() { diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Color.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Color.kt index 29be551..56ea04b 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Color.kt +++ b/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Color.kt @@ -3,4 +3,9 @@ package com.plcoding.weatherapp.presentation.ui.theme import androidx.compose.ui.graphics.Color val DarkBlue = Color(0xFF1B3B5A) -val DeepBlue = Color(0xFF102840) \ No newline at end of file +val DeepBlue = Color(0xFF102840) +val DeepBlueVariant = Color(0xFF4281C0) +val Pink80 = Color(0xFFEFB8C8) + +val HotColor = Color(0xFFDB9225) +val CoolColor = Color(0xFF87CEEB) \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Shape.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Shape.kt deleted file mode 100644 index 92ffb9f..0000000 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Shape.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.plcoding.weatherapp.presentation.ui.theme - -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Shapes -import androidx.compose.ui.unit.dp - -val Shapes = Shapes( - small = RoundedCornerShape(4.dp), - medium = RoundedCornerShape(4.dp), - large = RoundedCornerShape(0.dp) -) \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Theme.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Theme.kt index ba1d274..886623b 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Theme.kt +++ b/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Theme.kt @@ -1,13 +1,51 @@ package com.plcoding.weatherapp.presentation.ui.theme -import androidx.compose.material.MaterialTheme +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.ViewCompat + +private val DarkColorScheme = darkColorScheme( + primary = DeepBlue, + secondary = DeepBlueVariant, + tertiary = Pink80 +) @Composable -fun WeatherAppTheme(content: @Composable () -> Unit) { +fun WeatherAppTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + darkTheme -> DarkColorScheme + else -> DarkColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + (view.context as Activity).window.statusBarColor = colorScheme.primaryContainer.toArgb() + ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = !darkTheme + } + } + MaterialTheme( + colorScheme = colorScheme, typography = Typography, - shapes = Shapes, content = content ) } \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Type.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Type.kt index 2d80614..27403b5 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Type.kt +++ b/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Type.kt @@ -1,6 +1,6 @@ package com.plcoding.weatherapp.presentation.ui.theme -import androidx.compose.material.Typography +import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -8,21 +8,27 @@ import androidx.compose.ui.unit.sp // Set of Material typography styles to start with val Typography = Typography( - body1 = TextStyle( + bodyLarge = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, - fontSize = 16.sp + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp ) /* Other default text styles to override - button = TextStyle( + titleLarge = TextStyle( fontFamily = FontFamily.Default, - fontWeight = FontWeight.W500, - fontSize = 14.sp + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp ), - caption = TextStyle( + labelSmall = TextStyle( fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 12.sp + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp ) */ ) \ No newline at end of file diff --git a/build.gradle b/build.gradle index ba794a9..9e0472c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,12 @@ buildscript { ext { - compose_version = '1.1.1' - } - dependencies { - classpath "com.google.dagger:hilt-android-gradle-plugin:2.40.5" + compose_version = '1.1.0-beta01' } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.1.0' apply false - id 'com.android.library' version '7.1.0' apply false - id 'org.jetbrains.kotlin.android' version '1.6.10' apply false + id 'com.android.application' version '7.2.1' apply false + id 'com.android.library' version '7.2.1' apply false + id 'org.jetbrains.kotlin.android' version '1.5.31' apply false } task clean(type: Delete) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c15336a..628d35c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Jun 26 11:26:31 CEST 2022 +#Tue Jul 12 15:28:35 IST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME