dependencies {
implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.6")
implementation("androidx.activity:activity-compose:1.9.3")
implementation(platform("androidx.compose:compose-bom:2025.xx.xx"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling")
// Hilt
implementation("com.google.dagger:hilt-android:2.52")
ksp("com.google.dagger:hilt-compiler:2.52")
implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
// Coroutines + Flow
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
// Room
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
ksp("androidx.room:room-compiler:2.6.1")
// Retrofit + Converter
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-gson:2.11.0")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
// Coil for images
implementation("io.coil-kt:coil-compose:2.7.0")
}app/
|-- data/
| |-- local/ # Room DAO, Entity
| |-- remote/ # Retrofit API, DTO
| |-- repository/# Impl Repository
|-- domain/
| |-- model/ # Domain Entities
| |-- repository/# Abstract Repository
| |-- usecase/ # UseCases
|-- di/ # Hilt modules
|-- presentation/
| |-- ui/ # Screens, Composables
| |-- viewmodel/ # ViewModels
|-- util/data class Weather(
val city: String,
val temperature: Double,
val description: String,
val icon: String,
val timestamp: Long = System.currentTimeMillis()
)interface WeatherRepository {
suspend fun getWeather(city: String): Result<Weather>
fun getFavoriteCities(): Flow<List<String>>
suspend fun addFavorite(city: String)
suspend fun removeFavorite(city: String)
}class GetWeatherUseCase @Inject constructor(
private val repository: WeatherRepository
) {
suspend operator fun invoke(city: String): Result<Weather> = repository.getWeather(city)
}interface WeatherApi {
@GET("weather")
suspend fun getWeather(
@Query("q") city: String,
@Query("appid") apiKey: String,
@Query("units") units: String = "metric"
): WeatherResponse
}
// DTO
data class WeatherResponse(
val main: Main,
val weather: List<WeatherItem>,
val name: String
) {
data class Main(val temp: Double)
data class WeatherItem(val description: String, val icon: String)
}@Entity(tableName = "favorite_cities")
data class FavoriteCityEntity(
@PrimaryKey val city: String
)@Dao
interface FavoriteDao {
@Query("SELECT city FROM favorite_cities")
fun getAll(): Flow<List<String>>
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(city: FavoriteCityEntity)
@Delete
suspend fun delete(city: FavoriteCityEntity)
}@Singleton
class WeatherRepositoryImpl @Inject constructor(
private val api: WeatherApi,
private val dao: FavoriteDao,
private val apiKey: String // @Named або з BuildConfig
) : WeatherRepository {
override suspend fun getWeather(city: String): Result<Weather> = try {
val response = api.getWeather(city, apiKey)
val weather = Weather(
city = response.name,
temperature = response.main.temp,
description = response.weather.firstOrNull()?.description ?: "",
icon = response.weather.firstOrNull()?.icon ?: ""
)
Result.success(weather)
} catch (e: Exception) {
Result.failure(e)
}
override fun getFavoriteCities(): Flow<List<String>> = dao.getAll()
override suspend fun addFavorite(city: String) {
dao.insert(FavoriteCityEntity(city))
}
override suspend fun removeFavorite(city: String) {
dao.delete(FavoriteCityEntity(city))
}
}@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl("https://api.openweathermap.org/data/2.5/")
.addConverterFactory(GsonConverterFactory.create())
.build()
@Provides
@Singleton
fun provideWeatherApi(retrofit: Retrofit): WeatherApi = retrofit.create(WeatherApi::class.java)
// Room database vs.
}@HiltViewModel
class WeatherViewModel @Inject constructor(
private val getWeatherUseCase: GetWeatherUseCase,
private val repository: WeatherRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(WeatherUiState())
val uiState: StateFlow<WeatherUiState> = _uiState.asStateFlow()
private val _favorites = MutableStateFlow<List<String>>(emptyList())
val favorites: StateFlow<List<String>> = _favorites.asStateFlow()
init {
viewModelScope.launch {
repository.getFavoriteCities().collect { cities ->
_favorites.value = cities
}
}
}
fun searchCity(city: String) {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
getWeatherUseCase(city).fold(
onSuccess = { weather ->
_uiState.update { it.copy(weather = weather, error = null) }
},
onFailure = { e ->
_uiState.update { it.copy(error = e.message, isLoading = false) }
}
)
}
}
fun toggleFavorite(city: String) {
viewModelScope.launch {
if (_favorites.value.contains(city)) {
repository.removeFavorite(city)
} else {
repository.addFavorite(city)
}
}
}
}
data class WeatherUiState(
val weather: Weather? = null,
val isLoading: Boolean = false,
val error: String? = null
)@Composable
fun WeatherScreen(viewModel: WeatherViewModel = hiltViewModel()) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
val favorites by viewModel.favorites.collectAsStateWithLifecycle()
var city by remember { mutableStateOf("") }
Column(modifier = Modifier
.fillMaxSize()
.padding(16.dp)) {
TextField(
value = city,
onValueChange = { city = it },
label = { Text("Пошук міста (наприклад: Київ)") },
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.height(8.dp))
Button(onClick = { viewModel.searchCity(city) }) {
Text("Пошук")
}
if (state.isLoading) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.CenterHorizontally))
}
state.weather?.let { weather ->
Card(modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)) {
Column(modifier = Modifier.padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
Text(weather.city, style = MaterialTheme.typography.headlineMedium)
AsyncImage(
model = "https://openweathermap.org/img/wn/${weather.icon}@2x.png",
contentDescription = null,
modifier = Modifier.size(100.dp)
)
Text("${weather.temperature.toInt()}°C", style = MaterialTheme.typography.bodyLarge)
Text(weather.description.capitalize())
IconButton(onClick = { viewModel.toggleFavorite(weather.city) }) {
Icon(
imageVector = if (favorites.contains(weather.city)) Icons.Filled.Favorite else Icons.Outlined.Favorite,
contentDescription = "Улюблене"
)
}
}
}
}
state.error?.let {
Text(it, color = MaterialTheme.colorScheme.error)
}
// Список улюблених...
}
}const користувач = {
ім_я: "Олег",
вік: 25,
встановитиІм_я(нове_ім_я) {
this.ім_я = нове_ім_я;
return this; // Повертаємо об'єкт для ланцюжка
},
встановитиВік(новий_вік) {
this.вік = новий_вік;
return this; // Повертаємо об'єкт для ланцюжка
},
вивести() {
console.log(`Ім'я: ${this.ім_я}, Вік: ${this.вік}`);
return this; // Для продовження ланцюжка
}
};
// Використання ланцюжка методів
користувач
.встановитиІм_я("Марія")
.встановитиВік(30)
.вивести();Ім'я: Марія, Вік: 30користувач.встановитиІм_я("Іван");
користувач.встановитиВік(28);
користувач.вивести();const числа = [1, 2, 3, 4, 5, 6];
// Фільтруємо, множимо і виводимо результат
const результат = числа
.filter(num => num % 2 === 0) // Залишаємо парні числа
.map(num => num * 2) // Множимо на 2
.reduce((sum, num) => sum + num, 0); // Сума всіх чисел
console.log(результат); // 20 (4 + 8 + 12)class Калькулятор {
constructor(значення = 0) {
this.значення = значення;
}
додати(число) {
this.значення += число;
return this;
}
відняти(число) {
this.значення -= число;
return this;
}
отриматиРезультат() {
return this.значення;
}
}
const кальк = new Калькулятор();
const результат = кальк
.додати(10)
.додати(5)
.відняти(3)
.отриматиРезультат();
console.log(результат); // 12$("p")
.addClass("highlight")
.css("color", "blue")
.text("Привіт, світ!");while умова:
# Блок коду, який виконується, поки умова істинначисло = 5
while число > 0:
print(f"Число дорівнює: {число}")
число -= 1Число дорівнює: 5
Число дорівнює: 4
Число дорівнює: 3
Число дорівнює: 2
Число дорівнює: 1число = 10
while число > 0:
print(f"Число: {число}")
if число == 3:
break
число -= 1Число: 10
Число: 9
Число: 8
Число: 7
Число: 6
Число: 5
Число: 4
Число: 3число = 5
while число > 0:
число -= 1
if число == 2:
continue
print(f"Число: {число}")Число: 4
Число: 3
Число: 1
Число: 0while True:
print("Цикл працює!")
break # Без break цикл ніколи не зупинитьсяn = 5
факторіал = 1
while n > 0:
факторіал *= n
n -= 1
print(f"Факторіал дорівнює: {факторіал}")Факторіал дорівнює: 120