diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index 79ccf61..b268ef3 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,14 +4,6 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index 74dd639..b2c751a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9e90481..0525c5c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,11 +6,12 @@ + android:theme="@style/Theme.WeatherDemo.NoActionBar" + android:windowSoftInputMode="adjustResize|stateHidden"/> + + diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..7c755a9 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/example/weatherdemo/data/api/WeatherApiService.kt b/app/src/main/java/com/example/weatherdemo/data/api/WeatherApiService.kt index aa943e0..23e2ef4 100644 --- a/app/src/main/java/com/example/weatherdemo/data/api/WeatherApiService.kt +++ b/app/src/main/java/com/example/weatherdemo/data/api/WeatherApiService.kt @@ -1,22 +1,19 @@ package com.example.weatherdemo.data.api +import com.example.weatherdemo.data.models.WeatherForecastResponse import com.example.weatherdemo.data.models.WeatherResponse +import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Query -import retrofit2.Response - /** - * Retrofit service interface for Weather API + * Retrofit service interface for Weather API (WeatherAPI.com) * Defines the API endpoints and request methods */ interface WeatherApiService { /** * Fetches current weather data for a specific location - * @param key API key for authentication - * @param query Location query (city name, zip code, etc.) - * @param aqi Whether to include air quality index (yes/no) */ @GET("current.json") suspend fun getCurrentWeather( @@ -24,4 +21,17 @@ interface WeatherApiService { @Query("q") query: String, @Query("aqi") aqi: String = "yes" ): Response -} \ No newline at end of file + + /** + * Fetches forecast (current + hourly + daily) for a specific location + * @param days number of days forecast + */ + @GET("forecast.json") + suspend fun getForecastWeather( + @Query("key") key: String, + @Query("q") query: String, + @Query("days") days: Int = 3, + @Query("aqi") aqi: String = "no", + @Query("alerts") alerts: String = "no" + ): Response +} diff --git a/app/src/main/java/com/example/weatherdemo/data/models/WeatherForecastResponse.kt b/app/src/main/java/com/example/weatherdemo/data/models/WeatherForecastResponse.kt new file mode 100644 index 0000000..b368e1d --- /dev/null +++ b/app/src/main/java/com/example/weatherdemo/data/models/WeatherForecastResponse.kt @@ -0,0 +1,36 @@ +package com.example.weatherdemo.data.models + +/** + * Response model for WeatherAPI "forecast.json". + * This includes current weather + hourly & daily forecast. + */ +data class WeatherForecastResponse( + val location: Location, + val current: Current, + val forecast: Forecast +) + +data class Forecast( + val forecastday: List +) + +data class ForecastDay( + val date: String, + val day: Day, + val hour: List +) + +data class Day( + val maxtemp_c: Double, + val mintemp_c: Double, + val avgtemp_c: Double, + val condition: Condition, + val daily_chance_of_rain: Int? = null +) + +data class Hour( + val time: String, + val temp_c: Double, + val condition: Condition, + val chance_of_rain: Int? = null +) diff --git a/app/src/main/java/com/example/weatherdemo/data/repository/WeatherRepository.kt b/app/src/main/java/com/example/weatherdemo/data/repository/WeatherRepository.kt index 7b06255..66fc5f6 100644 --- a/app/src/main/java/com/example/weatherdemo/data/repository/WeatherRepository.kt +++ b/app/src/main/java/com/example/weatherdemo/data/repository/WeatherRepository.kt @@ -1,7 +1,7 @@ package com.example.weatherdemo.data.repository import com.example.weatherdemo.data.api.WeatherApiService -import com.example.weatherdemo.data.models.WeatherResponse +import com.example.weatherdemo.data.models.WeatherForecastResponse import com.example.weatherdemo.utils.Result import retrofit2.Response @@ -12,23 +12,24 @@ import retrofit2.Response class WeatherRepository(private val apiService: WeatherApiService) { /** - * Fetches weather data from API and returns a Result wrapper - * @param location The location to get weather for - * @return Result object containing either success or error + * Fetches forecast weather data from API and returns a Result wrapper. + * This uses WeatherAPI "forecast.json" so we can show hourly/daily prediction on main page. + * + * @param location The location to get weather for (city name, zip, lat/long) + * @param days number of forecast days (default 3) */ - suspend fun getWeatherData(location: String): Result { + suspend fun getWeatherForecast(location: String, days: Int = 3): Result { return try { - // Make API call - val response: Response = apiService.getCurrentWeather( + val response: Response = apiService.getForecastWeather( key = "822615b3cef1437bb0202739251712", // API key - query = location + query = location, + days = days ) - // Check if response is successful if (response.isSuccessful) { - val weatherResponse = response.body() - if (weatherResponse != null) { - Result.Success(weatherResponse) + val body = response.body() + if (body != null) { + Result.Success(body) } else { Result.Error(Exception("Empty response body")) } diff --git a/app/src/main/java/com/example/weatherdemo/ui/ChatActivity.kt b/app/src/main/java/com/example/weatherdemo/ui/ChatActivity.kt index 35e3e3b..66cfcb1 100644 --- a/app/src/main/java/com/example/weatherdemo/ui/ChatActivity.kt +++ b/app/src/main/java/com/example/weatherdemo/ui/ChatActivity.kt @@ -3,8 +3,12 @@ package com.example.weatherdemo.ui import android.os.Bundle import android.widget.Button import android.widget.EditText +import android.widget.TextView +import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.Toolbar +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.example.weatherdemo.R @@ -20,64 +24,67 @@ class ChatActivity : AppCompatActivity() { private lateinit var btnSend: Button private lateinit var recyclerView: RecyclerView private lateinit var chatAdapter: ChatAdapter - private lateinit var username: String // username user aktif + private lateinit var username: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + // ✅ penting: kita handle insets sendiri biar semua HP aman + WindowCompat.setDecorFitsSystemWindows(window, false) + setContentView(R.layout.activity_chat) - // Toolbar + tombol back - val toolbar = findViewById(R.id.toolbarChat) - setSupportActionBar(toolbar) - toolbar.setNavigationOnClickListener { finish() } - - // ✅ Ambil username dari Intent username = intent.getStringExtra("username") ?: "Guest" + findViewById(R.id.btnBack).setOnClickListener { finish() } + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { finish() } + }) + + val root = findViewById(R.id.chatRoot) + + // ✅ INI INTI FIX: kalau keyboard muncul, kasih padding bawah sebesar tinggi keyboard + ViewCompat.setOnApplyWindowInsetsListener(root) { v, insets -> + val ime = insets.getInsets(WindowInsetsCompat.Type.ime()) + val sysBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + + // padding atas biar tidak nabrak status bar, padding bawah ikut keyboard + v.setPadding( + v.paddingLeft, + sysBars.top, + v.paddingRight, + ime.bottom + ) + insets + } + etMessage = findViewById(R.id.etMessage) btnSend = findViewById(R.id.btnSend) recyclerView = findViewById(R.id.recyclerView) - val layoutManager = LinearLayoutManager(this).apply { - stackFromEnd = true - } - recyclerView.layoutManager = layoutManager - - // 🔥 WAJIB kirim username ke ChatAdapter + recyclerView.layoutManager = LinearLayoutManager(this).apply { stackFromEnd = true } chatAdapter = ChatAdapter(username) recyclerView.adapter = chatAdapter btnSend.setOnClickListener { val messageText = etMessage.text.toString().trim() - if (messageText.isNotEmpty()) { - - val time = SimpleDateFormat( - "HH:mm", - Locale.getDefault() - ).format(Date()) - - FirebaseUtils.sendMessage( - username = username, - message = messageText, - time = time - ) - + val time = SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date()) + FirebaseUtils.sendMessage(username = username, message = messageText, time = time) etMessage.setText("") } } + + // biar insets langsung diterapkan + ViewCompat.requestApplyInsets(root) } override fun onStart() { super.onStart() - FirebaseUtils.getMessages { messages -> chatAdapter.submitList(messages) - if (messages.isNotEmpty()) { - recyclerView.post { - recyclerView.scrollToPosition(messages.size - 1) - } + recyclerView.post { recyclerView.scrollToPosition(messages.size - 1) } } } } diff --git a/app/src/main/java/com/example/weatherdemo/ui/LoginActivity.kt b/app/src/main/java/com/example/weatherdemo/ui/LoginActivity.kt index 82228af..b040057 100644 --- a/app/src/main/java/com/example/weatherdemo/ui/LoginActivity.kt +++ b/app/src/main/java/com/example/weatherdemo/ui/LoginActivity.kt @@ -2,38 +2,33 @@ package com.example.weatherdemo.ui import android.content.Intent import android.os.Bundle -import android.widget.Button import android.widget.EditText import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import com.example.weatherdemo.R +import com.google.android.material.button.MaterialButton class LoginActivity : AppCompatActivity() { - private lateinit var etUsername: EditText - private lateinit var btnLogin: Button - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) - etUsername = findViewById(R.id.etUsername) - btnLogin = findViewById(R.id.btnLogin) + val etUsername = findViewById(R.id.etUsername) + val btnLogin = findViewById(R.id.btnLogin) btnLogin.setOnClickListener { val username = etUsername.text.toString().trim() - if (username.isNotEmpty()) { - val intent = Intent(this, MainActivity::class.java) - - // ✅ FIX: key KONSISTEN - intent.putExtra("username", username) - - startActivity(intent) - finish() - } else { - Toast.makeText(this, "Please enter a username", Toast.LENGTH_SHORT).show() + if (username.isEmpty()) { + Toast.makeText(this, "Masukkan username dulu", Toast.LENGTH_SHORT).show() + return@setOnClickListener } + + val intent = Intent(this, MainActivity::class.java) + intent.putExtra("username", username) + startActivity(intent) + finish() } } } diff --git a/app/src/main/java/com/example/weatherdemo/ui/MainActivity.kt b/app/src/main/java/com/example/weatherdemo/ui/MainActivity.kt index db8f87b..2465c3e 100644 --- a/app/src/main/java/com/example/weatherdemo/ui/MainActivity.kt +++ b/app/src/main/java/com/example/weatherdemo/ui/MainActivity.kt @@ -6,23 +6,25 @@ import android.os.Bundle import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.EditText -import android.widget.LinearLayout +import android.widget.ImageButton +import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView import android.widget.Toast import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.ContextCompat -import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.example.weatherdemo.R import com.example.weatherdemo.data.api.RetrofitInstance -import com.example.weatherdemo.data.models.WeatherResponse +import com.example.weatherdemo.data.models.WeatherForecastResponse import com.example.weatherdemo.data.repository.WeatherRepository +import com.example.weatherdemo.ui.adapter.DailyForecastAdapter +import com.example.weatherdemo.ui.adapter.HourlyForecastAdapter import com.example.weatherdemo.viewmodel.WeatherViewModel import com.example.weatherdemo.viewmodel.WeatherViewModelFactory import com.google.android.material.button.MaterialButton import com.google.android.material.card.MaterialCardView -import com.google.android.material.floatingactionbutton.FloatingActionButton class MainActivity : AppCompatActivity() { @@ -30,26 +32,43 @@ class MainActivity : AppCompatActivity() { WeatherViewModelFactory(WeatherRepository(RetrofitInstance.apiService)) } + private lateinit var hourlyAdapter: HourlyForecastAdapter + private lateinit var dailyAdapter: DailyForecastAdapter + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - // ✅ FIX DI SINI SAJA val username = intent.getStringExtra("username") ?: "Guest" + findViewById(R.id.tvWelcomeMessage).text = "Welcome, $username!" - val tvWelcomeMessage = findViewById(R.id.tvWelcomeMessage) - tvWelcomeMessage.text = "Welcome, $username!" - - // FAB chat → kirim username ke ChatActivity - findViewById(R.id.fabChat).setOnClickListener { + // ✅ CHAT GLOBAL → ImageButton (BUKAN FloatingActionButton) + findViewById(R.id.fabChat).setOnClickListener { val i = Intent(this, ChatActivity::class.java) i.putExtra("username", username) startActivity(i) } + initForecastLists() initViews() setupObservers() - viewModel.fetchWeatherData("London") + + // default city (AMAN) + viewModel.fetchForecast("Bekasi", days = 3) + } + + private fun initForecastLists() { + hourlyAdapter = HourlyForecastAdapter() + findViewById(R.id.rvHourlyForecast).apply { + layoutManager = LinearLayoutManager(this@MainActivity, LinearLayoutManager.HORIZONTAL, false) + adapter = hourlyAdapter + } + + dailyAdapter = DailyForecastAdapter() + findViewById(R.id.rvDailyForecast).apply { + layoutManager = LinearLayoutManager(this@MainActivity) + adapter = dailyAdapter + } } private fun initViews() { @@ -60,7 +79,7 @@ class MainActivity : AppCompatActivity() { val location = etSearch.text.toString().trim() if (location.isNotEmpty()) { hideKeyboard() - viewModel.fetchWeatherData(location) + viewModel.fetchForecast(location, days = 3) } else { Toast.makeText(this, "Please enter a location", Toast.LENGTH_SHORT).show() } @@ -68,78 +87,73 @@ class MainActivity : AppCompatActivity() { } private fun setupObservers() { - viewModel.weatherData.observe(this, Observer { weatherData -> - weatherData?.let { updateWeatherUI(it) } - }) + viewModel.forecastData.observe(this) { forecast -> + forecast?.let { updateWeatherUI(it) } + } - viewModel.isLoading.observe(this, Observer { isLoading -> + viewModel.isLoading.observe(this) { isLoading -> findViewById(R.id.progressBar).visibility = if (isLoading) View.VISIBLE else View.GONE - }) + } - viewModel.errorMessage.observe(this, Observer { errorMessage -> - val tvError = findViewById(R.id.tvError) - if (errorMessage.isNotEmpty()) { - tvError.text = errorMessage - tvError.visibility = View.VISIBLE - findViewById(R.id.weatherCard).visibility = View.GONE - Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show() - } else { - tvError.visibility = View.GONE - } - }) - } - - private fun updateWeatherUI(weatherResponse: WeatherResponse) { - val weatherCard = findViewById(R.id.weatherCard) - val tvLocation = findViewById(R.id.tvLocation) - val tvTemperature = findViewById(R.id.tvTemperature) - val tvCondition = findViewById(R.id.tvCondition) - val tvFeelsLike = findViewById(R.id.tvFeelsLike) - val tvHumidity = findViewById(R.id.tvHumidity) - val tvWind = findViewById(R.id.tvWind) - - tvLocation.text = "${weatherResponse.location.name}, ${weatherResponse.location.country}" - tvTemperature.text = "${weatherResponse.current.temp_c}°C" - tvCondition.text = weatherResponse.current.condition.text - tvFeelsLike.text = "${weatherResponse.current.feelslike_c}°C" - tvHumidity.text = "${weatherResponse.current.humidity}%" - tvWind.text = "${weatherResponse.current.wind_kph} km/h" - - val condition = weatherResponse.current.condition.text.lowercase() - val mainLayout = findViewById(R.id.mainLayout) - - when { - condition.contains("clear") || condition.contains("sunny") -> { - weatherCard.setCardBackgroundColor(ContextCompat.getColor(this, R.color.sunny)) - mainLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.sunny_background)) - } - condition.contains("rain") -> { - weatherCard.setCardBackgroundColor(ContextCompat.getColor(this, R.color.rainy)) - mainLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.rainy_background)) - } - condition.contains("cloudy") -> { - weatherCard.setCardBackgroundColor(ContextCompat.getColor(this, R.color.cloudy)) - mainLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.cloudy_background)) - } - condition.contains("snow") -> { - weatherCard.setCardBackgroundColor(ContextCompat.getColor(this, R.color.snowy)) - mainLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.snowy_background)) - } - else -> { - weatherCard.setCardBackgroundColor(ContextCompat.getColor(this, R.color.default_weather)) - mainLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.default_weather_background)) + viewModel.errorMessage.observe(this) { errorMessage -> + findViewById(R.id.tvError).apply { + text = errorMessage + visibility = if (errorMessage.isNullOrBlank()) View.GONE else View.VISIBLE } } + } + + private fun updateWeatherUI(weather: WeatherForecastResponse) { + val weatherCard = findViewById(R.id.weatherCard) + val ivBg = findViewById(R.id.ivBg) + + findViewById(R.id.tvLocation).text = + "${weather.location.name}, ${weather.location.country}" + findViewById(R.id.tvTemperature).text = + "${weather.current.temp_c}°C" + findViewById(R.id.tvCondition).text = + weather.current.condition.text + findViewById(R.id.tvFeelsLike).text = + "${weather.current.feelslike_c}°C" + findViewById(R.id.tvHumidity).text = + "${weather.current.humidity}%" + findViewById(R.id.tvWind).text = + "${weather.current.wind_kph} km/h" + + // ✅ BACKGROUND FIX (no typo) + val condition = weather.current.condition.text.lowercase() + when { + condition.contains("sunny") || condition.contains("clear") -> + ivBg.setImageResource(R.drawable.sunny) + + condition.contains("rain") || condition.contains("drizzle") || condition.contains("thunder") -> + ivBg.setImageResource(R.drawable.rainy) + + condition.contains("cloud") || condition.contains("overcast") || condition.contains("mist") || condition.contains("fog") -> + ivBg.setImageResource(R.drawable.cloudy) + + condition.contains("snow") || condition.contains("sleet") || condition.contains("blizzard") -> + ivBg.setImageResource(R.drawable.snowy) + + else -> + ivBg.setImageResource(R.drawable.defaultt) + } + + // forecast + val hourly = weather.forecast.forecastday.firstOrNull()?.hour.orEmpty().take(12) + hourlyAdapter.submitList(hourly) + + dailyAdapter.submitList(weather.forecast.forecastday) weatherCard.visibility = View.VISIBLE } private fun hideKeyboard() { - val view = this.currentFocus - view?.let { + currentFocus?.let { val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(it.windowToken, 0) + it.clearFocus() } } } diff --git a/app/src/main/java/com/example/weatherdemo/ui/adapter/DailyForecastAdapter.kt b/app/src/main/java/com/example/weatherdemo/ui/adapter/DailyForecastAdapter.kt new file mode 100644 index 0000000..b125ee5 --- /dev/null +++ b/app/src/main/java/com/example/weatherdemo/ui/adapter/DailyForecastAdapter.kt @@ -0,0 +1,43 @@ +package com.example.weatherdemo.ui.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.example.weatherdemo.R +import com.example.weatherdemo.data.models.ForecastDay + +class DailyForecastAdapter( + private var items: List = emptyList() +) : RecyclerView.Adapter() { + + fun submitList(newItems: List) { + items = newItems + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DayVH { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_day_forecast, parent, false) + return DayVH(view) + } + + override fun onBindViewHolder(holder: DayVH, position: Int) { + holder.bind(items[position]) + } + + override fun getItemCount(): Int = items.size + + class DayVH(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val tvDate: TextView = itemView.findViewById(R.id.tvDate) + private val tvRange: TextView = itemView.findViewById(R.id.tvTempRange) + private val tvCondition: TextView = itemView.findViewById(R.id.tvDayCondition) + + fun bind(item: ForecastDay) { + tvDate.text = item.date + tvRange.text = "${item.day.mintemp_c}°C - ${item.day.maxtemp_c}°C" + tvCondition.text = item.day.condition.text + } + } +} diff --git a/app/src/main/java/com/example/weatherdemo/ui/adapter/HourlyForecastAdapter.kt b/app/src/main/java/com/example/weatherdemo/ui/adapter/HourlyForecastAdapter.kt new file mode 100644 index 0000000..52610e3 --- /dev/null +++ b/app/src/main/java/com/example/weatherdemo/ui/adapter/HourlyForecastAdapter.kt @@ -0,0 +1,45 @@ +package com.example.weatherdemo.ui.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.example.weatherdemo.R +import com.example.weatherdemo.data.models.Hour + +class HourlyForecastAdapter( + private var items: List = emptyList() +) : RecyclerView.Adapter() { + + fun submitList(newItems: List) { + items = newItems + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HourVH { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_hour_forecast, parent, false) + return HourVH(view) + } + + override fun onBindViewHolder(holder: HourVH, position: Int) { + holder.bind(items[position]) + } + + override fun getItemCount(): Int = items.size + + class HourVH(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val tvHour: TextView = itemView.findViewById(R.id.tvHour) + private val tvTemp: TextView = itemView.findViewById(R.id.tvHourTemp) + private val tvCondition: TextView = itemView.findViewById(R.id.tvHourCondition) + + fun bind(item: Hour) { + // item.time example: "2025-12-29 13:00" + val hourText = item.time.substringAfter(" ").ifBlank { item.time } + tvHour.text = hourText + tvTemp.text = "${item.temp_c}°C" + tvCondition.text = item.condition.text + } + } +} diff --git a/app/src/main/java/com/example/weatherdemo/viewmodel/WeatherViewModel.kt b/app/src/main/java/com/example/weatherdemo/viewmodel/WeatherViewModel.kt index 81e5f11..f23f389 100644 --- a/app/src/main/java/com/example/weatherdemo/viewmodel/WeatherViewModel.kt +++ b/app/src/main/java/com/example/weatherdemo/viewmodel/WeatherViewModel.kt @@ -4,10 +4,10 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.example.weatherdemo.data.models.WeatherResponse +import com.example.weatherdemo.data.models.WeatherForecastResponse import com.example.weatherdemo.data.repository.WeatherRepository -import kotlinx.coroutines.launch import com.example.weatherdemo.utils.Result +import kotlinx.coroutines.launch /** * ViewModel class that prepares and manages UI-related data @@ -16,9 +16,9 @@ import com.example.weatherdemo.utils.Result */ class WeatherViewModel(private val repository: WeatherRepository) : ViewModel() { - // LiveData for weather information - observed by UI - private val _weatherData = MutableLiveData() - val weatherData: LiveData = _weatherData + // LiveData for forecast response (current + hourly + daily) + private val _forecastData = MutableLiveData() + val forecastData: LiveData = _forecastData // LiveData for loading state - to show/hide progress bar private val _isLoading = MutableLiveData() @@ -29,17 +29,15 @@ class WeatherViewModel(private val repository: WeatherRepository) : ViewModel() val errorMessage: LiveData = _errorMessage /** - * Fetches weather data for a given location - * Uses coroutines for background operations - * @param location The city name to search for + * Fetches forecast data for a given location */ - fun fetchWeatherData(location: String) { + fun fetchForecast(location: String, days: Int = 3) { _isLoading.value = true viewModelScope.launch { - when (val result = repository.getWeatherData(location)) { + when (val result = repository.getWeatherForecast(location, days)) { is Result.Success -> { - _weatherData.value = result.data + _forecastData.value = result.data _errorMessage.value = "" } is Result.Error -> { @@ -49,4 +47,4 @@ class WeatherViewModel(private val repository: WeatherRepository) : ViewModel() _isLoading.value = false } } -} \ No newline at end of file +} diff --git a/app/src/main/res/drawable/ic_weatherhub.webp b/app/src/main/res/drawable/ic_weatherhub.webp new file mode 100644 index 0000000..1efb2e5 Binary files /dev/null and b/app/src/main/res/drawable/ic_weatherhub.webp differ diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index 58c12cb..77eafa4 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -1,53 +1,90 @@ - + android:layout_height="match_parent"> - - + + android:background="#2F55F4" + android:paddingStart="12dp" + android:paddingEnd="12dp" + android:gravity="center_vertical" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent"> - + + + + + + + app:layout_constraintTop_toBottomOf="@id/topBar" + app:layout_constraintBottom_toTopOf="@id/inputBar" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> - - + + android:orientation="horizontal" + android:padding="8dp" + android:background="#F2F2F2" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent"> - -