From e52798ae2adf1911b25b09f7658b148206b6d45f Mon Sep 17 00:00:00 2001 From: Alex Pan Date: Thu, 7 Apr 2022 11:31:24 +0300 Subject: [PATCH 1/2] homework 3. network --- HomeWorkNetwork/app/build.gradle | 17 +++++++++- .../java/com/pg/homeworknetwork/Models.kt | 28 ++++++++++++++-- .../pg/homeworknetwork/MovieDetailFragment.kt | 24 ++++++++++---- .../pg/homeworknetwork/MovieListFragment.kt | 14 ++++++-- .../java/com/pg/homeworknetwork/Network.kt | 33 ++++++++++++++++++- HomeworkTemplate/build.gradle | 4 +-- 6 files changed, 105 insertions(+), 15 deletions(-) diff --git a/HomeWorkNetwork/app/build.gradle b/HomeWorkNetwork/app/build.gradle index d5e6ad0..c160feb 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", '"8c36877bfc3c8db80235da73a301a630"' buildConfigField "String", "API_IMAGE_BASE_URL", '"https://image.tmdb.org/t/p/w500"' applicationId "com.pg.homeworknetwork" @@ -46,4 +46,19 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + + //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 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2' + implementation 'io.ktor:ktor-client-logging-jvm:1.6.7' + + + //Coroutine + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0' + + //test + implementation 'androidx.test.ext:junit-ktx:1.1.3' } \ 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..3bb96dc 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,29 @@ package com.pg.homeworknetwork +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable -class Movies +@Serializable +data class Movies ( + @SerialName("page") + val page: Int, + @SerialName("results") + val movies: MutableList +) -class Movie \ No newline at end of file +@Serializable +data class Movie ( + @SerialName("id") + val id: Int, + @SerialName("title") + val title: String, + @SerialName("poster_path") + val posterPath: String, + @SerialName("original_title") + val originalTitle: String?, + @SerialName("overview") + val overview: String?, + @SerialName("popularity") + val popularity: Double?, + @SerialName("release_date") + val releaseDate: String? +) \ 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..7d30776 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.* +import kotlin.coroutines.CoroutineContext class MovieDetailFragment : Fragment(R.layout.fragment_movie_preview) { lateinit var poster: ImageView @@ -18,6 +20,9 @@ class MovieDetailFragment : Fragment(R.layout.fragment_movie_preview) { lateinit var popularity: TextView lateinit var releaseDate: TextView + private val context: CoroutineContext = SupervisorJob() + Dispatchers.IO + private val scope = CoroutineScope(context) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) with(view) { @@ -29,14 +34,19 @@ 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)) + scope.launch { + //TODO получаем фильм + val movie = MovieApi().getMovieById(movieId) + withContext(Dispatchers.Main) { + 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..fd0663b 100644 --- a/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieListFragment.kt +++ b/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieListFragment.kt @@ -7,9 +7,16 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext class MovieListFragment : Fragment(R.layout.fragment_movie_list) { lateinit var recycler: RecyclerView + private val context: CoroutineContext = SupervisorJob() + Dispatchers.IO + private val scope = CoroutineScope(context) private val goToDetails = object : MovieItemAdapter.IOnItemClick { override fun onItemClick(movie: Movie) { @@ -31,8 +38,11 @@ class MovieListFragment : Fragment(R.layout.fragment_movie_list) { } } val adapter = (recycler.adapter as MovieItemAdapter) - val movies = //получаем фильмы - adapter.submitList(movies) + //TODO получаем фильмы + scope.launch { + val movies = MovieApi().getListMovies() + adapter.submitList(movies.movies) + } 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..6575735 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,36 @@ package com.pg.homeworknetwork -class Api { +import io.ktor.client.* +import io.ktor.client.engine.android.* +import io.ktor.client.features.json.* +import io.ktor.client.features.json.serializer.* +import io.ktor.client.request.* +import kotlinx.serialization.json.Json as serialJson +class MovieApi { + private val client = HttpClient(Android){ + install(JsonFeature) { + serializer = KotlinxSerializer(serialJson { + prettyPrint = true + isLenient = true + ignoreUnknownKeys = true + }) + } + } + + suspend fun getListMovies(): Movies = client.get( + host = BuildConfig.API_BASE_URL, + path = "3/movie/popular" + ) { + parameter("api_key", BuildConfig.API_KEY) + parameter("language", "ru") + } + + suspend fun getMovieById(id: Int): Movie = client.get( + host = BuildConfig.API_BASE_URL, + path = "3/movie/${id}" + ) { + parameter("api_key", BuildConfig.API_KEY) + parameter("language", "ru") + } } \ No newline at end of file diff --git a/HomeworkTemplate/build.gradle b/HomeworkTemplate/build.gradle index 84ed49d..0ccd671 100644 --- a/HomeworkTemplate/build.gradle +++ b/HomeworkTemplate/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.1.1' apply false - id 'com.android.library' version '7.1.1' apply false + id 'com.android.application' version '7.1.2' apply false + id 'com.android.library' version '7.1.2' apply false id 'org.jetbrains.kotlin.android' version '1.6.10' apply false } From 0808bd620b0f2498a554c6b6ac880aa0297799b2 Mon Sep 17 00:00:00 2001 From: Alex Pan Date: Mon, 16 May 2022 01:52:25 +0300 Subject: [PATCH 2/2] homework 4. dataBase --- .../epicdatabaseexample/db/NoteDatabase.kt | 9 ++++- .../epicdatabaseexample/db/note/NoteDao.kt | 5 +++ .../epicdatabaseexample/db/note/NoteEntity.kt | 3 ++ .../ui/note_list/NoteDiffUtilCallback.kt | 15 +++++++- .../ui/note_list/NoteListAdapter.kt | 36 ++++++++++++++----- .../ui/note_list/NoteListViewModel.kt | 4 +++ 6 files changed, 61 insertions(+), 11 deletions(-) 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..4da8a40 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, @@ -49,6 +49,13 @@ abstract class NoteDatabase : RoomDatabase() { } }, // 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..e581b6f 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 @@ -30,4 +30,9 @@ abstract class NoteDao { // Что можно указать в качестве возвращаемого типа? // Напомню, что выше есть метод observeNoteList, который будет вызываться при любом // изменении списка в Базе Данных. + @Update + abstract fun updateNote(note: NoteEntity): Completable + + @Update + abstract fun updateAll(notes: List): 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..fe65c11 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 @@ -26,6 +26,8 @@ data class NoteEntity( // В SQLite не поддерживается Boolean, но в Room его можно использовать. // Поэтому поле делаем именно Boolean, а при добавлении миграции - нужно будет погуглить, // как обойти это ограничение. + @ColumnInfo(name = COLUMN_IS_COMPLETED) + var isCompleted: Boolean = false ) { companion object { @@ -33,5 +35,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/NoteDiffUtilCallback.kt b/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/ui/note_list/NoteDiffUtilCallback.kt index 7c8c9af..550cd01 100644 --- a/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/ui/note_list/NoteDiffUtilCallback.kt +++ b/EpicDatabseExample/app/src/main/java/com/example/epicdatabaseexample/ui/note_list/NoteDiffUtilCallback.kt @@ -5,11 +5,24 @@ import com.example.epicdatabaseexample.db.note.NoteEntity class NoteDiffUtilCallback : DiffUtil.ItemCallback() { + data class Payload( + val isCompleteChanged: Boolean + ) + override fun areItemsTheSame(oldItem: NoteEntity, newItem: NoteEntity): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: NoteEntity, newItem: NoteEntity): Boolean { - return false + return oldItem == newItem + } + + //из лекции + override fun getChangePayload(oldItem: NoteEntity, newItem: NoteEntity): Any? { + val isCompletedChanged = oldItem.isCompleted != newItem.isCompleted + return if (isCompletedChanged) + Payload(isCompletedChanged) + else null //можно удалить } + } \ 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..bba2c2e 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 @@ -21,14 +21,32 @@ class NoteListAdapter( } 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 + } + } + + //из лекции + override fun onBindViewHolder( + holder: NoteViewHolder, + position: Int, + payloads: MutableList + ) { + val payload: NoteDiffUtilCallback.Payload? = + payloads.firstOrNull() as? NoteDiffUtilCallback.Payload + if (payload != null) { + if (payload.isCompleteChanged) { + val note = getItem(position) + holder.isCompleted.isChecked = note.isCompleted + } + } else { + super.onBindViewHolder(holder, position, payloads) + } } } \ 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..26b36ca 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 @@ -65,6 +65,10 @@ class NoteListViewModel constructor( fun onIsCompletedClick(note: NoteEntity) { // TODO Добавить вызов noteDao для обновления элемента в таблице. + compositeDisposable.add(noteDao.updateNote(note) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe()) } override fun onCleared() {