diff --git a/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/db/NoteDatabase.kt b/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/db/NoteDatabase.kt index 1c213b2..98d6ee5 100644 --- a/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/db/NoteDatabase.kt +++ b/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/db/NoteDatabase.kt @@ -12,7 +12,7 @@ import com.example.epicdatabaseexample.db.note.NoteDao import com.example.epicdatabaseexample.db.note.NoteEntity @Database( - version = 2, + version = 3, exportSchema = false, entities = [ NoteEntity::class, @@ -48,7 +48,13 @@ abstract class NoteDatabase : RoomDatabase() { "DEFAULT ('${PersonEntity.DEFAULT_PERSON_NAME}')") } }, - // TODO Добавить миграцию. + object : Migration(2, 3) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE $NOTE_TABLE " + + "ADD COLUMN ${NoteEntity.COLUMN_IS_COMPLETED} INTEGER NOT NULL " + + "DEFAULT 0") + } + }, ) private fun createDatabaseInstance(application: Application): NoteDatabase { diff --git a/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/db/note/NoteDao.kt b/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/db/note/NoteDao.kt index 8645616..936616f 100644 --- a/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/db/note/NoteDao.kt +++ b/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/db/note/NoteDao.kt @@ -26,8 +26,7 @@ abstract class NoteDao { @Query("DELETE FROM note_table") abstract fun deleteAllNote(): Completable - // TODO Добавить метод для обновления одного элемента. - // Что можно указать в качестве возвращаемого типа? - // Напомню, что выше есть метод observeNoteList, который будет вызываться при любом - // изменении списка в Базе Данных. + @Update + abstract fun updateNote(note: NoteEntity): Completable + } \ No newline at end of file diff --git a/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/db/note/NoteEntity.kt b/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/db/note/NoteEntity.kt index b42b9b3..4d791a8 100644 --- a/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/db/note/NoteEntity.kt +++ b/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/db/note/NoteEntity.kt @@ -22,10 +22,9 @@ data class NoteEntity( @ColumnInfo(name = COLUMN_PERSON_NAME) var personName: String = PersonEntity.DEFAULT_PERSON_NAME, - // TODO Нужно добавить нове поле isCompleted типа Boolean. - // В SQLite не поддерживается Boolean, но в Room его можно использовать. - // Поэтому поле делаем именно Boolean, а при добавлении миграции - нужно будет погуглить, - // как обойти это ограничение. + @ColumnInfo(name = COLUMN_IS_COMPLETED) + var isCompleted: Boolean = false, + ) { companion object { @@ -33,5 +32,6 @@ data class NoteEntity( const val COLUMN_TITLE = "title" const val COLUMN_DESCRIPTION = "descriptions" const val COLUMN_PERSON_NAME = "person_name" + const val COLUMN_IS_COMPLETED = "is_completed" } } \ No newline at end of file diff --git a/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/ui/note_list/NoteListAdapter.kt b/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/ui/note_list/NoteListAdapter.kt index 192174f..73cbd79 100644 --- a/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/ui/note_list/NoteListAdapter.kt +++ b/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/ui/note_list/NoteListAdapter.kt @@ -20,15 +20,14 @@ class NoteListAdapter( onNoteClick.invoke(note.id) } holder.personName.text = note.personName - // TODO Раскомментировать, после добавления нового поля в NoteEntity. -// holder.isCompleted.setOnClickListener { -// onIsCompletedClick.invoke(note.copy( -// isCompleted = !note.isCompleted -// )) -// } -// val isCompletedChanged = note.isCompleted != holder.isCompleted.isChecked -// if (isCompletedChanged) { -// holder.isCompleted.isChecked = note.isCompleted -// } + holder.isCompleted.setOnClickListener { + onIsCompletedClick.invoke(note.copy( + isCompleted = !note.isCompleted + )) + } + val isCompletedChanged = note.isCompleted != holder.isCompleted.isChecked + if (isCompletedChanged) { + holder.isCompleted.isChecked = note.isCompleted + } } } \ No newline at end of file diff --git a/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/ui/note_list/NoteListViewModel.kt b/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/ui/note_list/NoteListViewModel.kt index 3344f2c..7c2c6ca 100644 --- a/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/ui/note_list/NoteListViewModel.kt +++ b/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/ui/note_list/NoteListViewModel.kt @@ -64,7 +64,12 @@ class NoteListViewModel constructor( } fun onIsCompletedClick(note: NoteEntity) { - // TODO Добавить вызов noteDao для обновления элемента в таблице. + compositeDisposable.add( + noteDao.updateNote(note) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { } + ) } override fun onCleared() { diff --git a/HomeWorkNetwork/app/build.gradle b/HomeWorkNetwork/app/build.gradle index d5e6ad0..08b499a 100644 --- a/HomeWorkNetwork/app/build.gradle +++ b/HomeWorkNetwork/app/build.gradle @@ -10,7 +10,7 @@ android { defaultConfig { buildConfigField "String", "API_BASE_URL", '"api.themoviedb.org"' - buildConfigField "String", "API_KEY", '"/*ваш ap_key*/"' + buildConfigField "String", "API_KEY", '"api_key"' buildConfigField "String", "API_IMAGE_BASE_URL", '"https://image.tmdb.org/t/p/w500"' applicationId "com.pg.homeworknetwork" @@ -41,9 +41,32 @@ dependencies { //Coil (загрузка постеров) implementation "io.coil-kt:coil-compose:2.0.0-rc02" + implementation 'com.google.android.material:material:1.5.0' + + //Coroutine + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0' + + //KTOR + implementation "io.ktor:ktor-client-core:1.6.7" + implementation "io.ktor:ktor-client-android:1.6.7" + implementation 'io.ktor:ktor-client-serialization:1.6.7' + implementation 'io.ktor:ktor-client-logging-jvm:1.6.7' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2' + //-------------------------------------------------------------------------------------------------------------- implementation 'androidx.core:core-ktx:1.7.0' - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.5.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation "androidx.compose.ui:ui:1.1.1" + implementation "androidx.compose.material:material:1.1.1" + implementation "androidx.compose.ui:ui-tooling-preview:1.1.1" + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1' + implementation 'androidx.activity:activity-compose:1.4.0' + + implementation "androidx.navigation:navigation-fragment:2.4.1" + implementation "androidx.navigation:navigation-ui:2.4.1" + implementation "androidx.navigation:navigation-fragment-ktx:2.4.1" + implementation "androidx.navigation:navigation-ui-ktx:2.4.1" + + debugImplementation "androidx.compose.ui:ui-tooling:1.1.1" + } \ No newline at end of file diff --git a/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/Models.kt b/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/Models.kt index 69df05a..05b5693 100644 --- a/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/Models.kt +++ b/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/Models.kt @@ -1,5 +1,40 @@ package com.pg.homeworknetwork -class Movies +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable -class Movie \ No newline at end of file +@Serializable +data class Movies( + val page: Int, + @SerialName("total_results") + val totalResults: Int, + @SerialName("total_pages") + val totalPages: Int, + val results: List +) {} + +@Serializable +data class Movie( + val id: Int, + @SerialName("poster_path") + val posterPath: String?, +// val adult: Boolean?, + val overview: String?, + @SerialName("release_date") + val releaseDate: String?, +// @SerialName("genre_ids") +// val genreIds: List? = null, + @SerialName("original_title") + val originalTitle: String?, +// @SerialName("original_language") +// val originalLanguage: String?, + val title: String?, +// @SerialName("backdrop_path") +// val backdropPath: String?, + val popularity: Float?, +// @SerialName("vote_count") +// val voteCount: Int?, +// val video: Boolean?, +// @SerialName("vote_average") +// val voteAverage: Float? +) {} \ No newline at end of file diff --git a/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieDetailFragment.kt b/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieDetailFragment.kt index 338ddc7..cb9caa8 100644 --- a/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieDetailFragment.kt +++ b/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieDetailFragment.kt @@ -10,6 +10,8 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import coil.load import coil.transform.RoundedCornersTransformation +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking class MovieDetailFragment : Fragment(R.layout.fragment_movie_preview) { lateinit var poster: ImageView @@ -17,6 +19,7 @@ class MovieDetailFragment : Fragment(R.layout.fragment_movie_preview) { lateinit var overview: TextView lateinit var popularity: TextView lateinit var releaseDate: TextView + val api: Api = Api() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -29,14 +32,18 @@ class MovieDetailFragment : Fragment(R.layout.fragment_movie_preview) { } val movieId = arguments?.getInt(ARG_ID) ?: 550 - val movie = //получаем фильм - poster.load("${BuildConfig.API_IMAGE_BASE_URL}${movie.posterPath}") { - transformations(RoundedCornersTransformation(16f)) + + runBlocking(Dispatchers.IO) { + api.getMovie(movieId) + }.let { movie -> + poster.load("${BuildConfig.API_IMAGE_BASE_URL}${movie.posterPath}") { + transformations(RoundedCornersTransformation(16f)) + } + originalTitle.text = movie.originalTitle + overview.text = movie.overview + popularity.text = movie.popularity.toString() + releaseDate.text = movie.releaseDate } - originalTitle.text = movie.originalTitle - overview.text = movie.overview - popularity.text = movie.popularity.toString() - releaseDate.text = movie.releaseDate activity?.onBackPressedDispatcher?.addCallback(this.viewLifecycleOwner, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { diff --git a/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieListFragment.kt b/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieListFragment.kt index 762a176..6b74fdd 100644 --- a/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieListFragment.kt +++ b/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieListFragment.kt @@ -7,10 +7,12 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking class MovieListFragment : Fragment(R.layout.fragment_movie_list) { lateinit var recycler: RecyclerView - + val api: Api = Api() private val goToDetails = object : MovieItemAdapter.IOnItemClick { override fun onItemClick(movie: Movie) { val manager: FragmentManager = parentFragmentManager @@ -31,8 +33,13 @@ class MovieListFragment : Fragment(R.layout.fragment_movie_list) { } } val adapter = (recycler.adapter as MovieItemAdapter) - val movies = //получаем фильмы - adapter.submitList(movies) + + runBlocking(Dispatchers.IO) { + api.getPopularMovies() + }?.let { + adapter.submitList(it.results) + } + super.onViewCreated(view, savedInstanceState) } diff --git a/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/Network.kt b/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/Network.kt index af25f9f..a8fa1c8 100644 --- a/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/Network.kt +++ b/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/Network.kt @@ -1,5 +1,59 @@ package com.pg.homeworknetwork +import io.ktor.client.* +import io.ktor.client.features.json.* +import io.ktor.client.features.json.serializer.* +import io.ktor.client.features.logging.* +import io.ktor.client.request.* + class Api { + private val baseUrl = "${BuildConfig.API_BASE_URL}" + private val movieDetailUrl = "3/movie/" + private val popularMoviesUrl = "3/movie/popular" + + val kLogger = object : Logger { + override fun log(message: String) { + if (message.contains("api_key=")) { + println("${message.split("=").first()}=BuildConfig.API_KEY") + } else { + println(message) + } + } + } + + private val client = HttpClient() { + + install(Logging) { + logger = kLogger + level = LogLevel.BODY + } + + install(JsonFeature) { + serializer = KotlinxSerializer(kotlinx.serialization.json.Json { + prettyPrint = true + isLenient = true + ignoreUnknownKeys = true + }) + } + } + + suspend fun getMovie(movieId: Int): Movie = client.use { + it.get( + host = baseUrl, + path = movieDetailUrl + movieId + ) { + parameter("api_key", BuildConfig.API_KEY) + } + } + + suspend fun getPopularMovies(): Movies? = client.use { + it.get( + host = baseUrl, + path = popularMoviesUrl + ) { + parameter("api_key", BuildConfig.API_KEY) + } + } + } \ No newline at end of file