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..f3f719a 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,6 +26,8 @@ abstract class NoteDao { @Query("DELETE FROM note_table") abstract fun deleteAllNote(): Completable + @Update + abstract fun updateNote(note: NoteEntity): Completable // TODO Добавить метод для обновления одного элемента. // Что можно указать в качестве возвращаемого типа? // Напомню, что выше есть метод observeNoteList, который будет вызываться при любом 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..9128b64 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,6 +22,9 @@ data class NoteEntity( @ColumnInfo(name = COLUMN_PERSON_NAME) var personName: String = PersonEntity.DEFAULT_PERSON_NAME, + @ColumnInfo(name = COLUMN_IS_COMPLETED) + var isCompleted: Boolean = false + // TODO Нужно добавить нове поле isCompleted типа Boolean. // В SQLite не поддерживается Boolean, но в Room его можно использовать. // Поэтому поле делаем именно Boolean, а при добавлении миграции - нужно будет погуглить, @@ -33,5 +36,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..d172523 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,14 @@ 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 + } } } \ 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..8070367 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,11 @@ 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..fc79c47 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", '"3126e52d8c1e19b81683e1f9c1ec0915"' buildConfigField "String", "API_IMAGE_BASE_URL", '"https://image.tmdb.org/t/p/w500"' applicationId "com.pg.homeworknetwork" @@ -46,4 +46,13 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + + 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' + + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0' } \ 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..3d6ba65 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,30 @@ 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( + @SerialName("page") + val page: Int, + @SerialName("results") + val results: List = ArrayList() +) + +@Serializable +data class Movie( + @SerialName("id") + val id: Int, + @SerialName("title") + val title: String? = null, + @SerialName("poster_path") + val posterPath: String? = null, + @SerialName("overview") + val overview: String? = null, + @SerialName("popularity") + val popularity: Double? = null, + @SerialName("original_title") + val originalTitle: String? = null, + @SerialName("release_date") + val releaseDate: String? = null +) 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..28d9fea 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,11 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import coil.load import coil.transform.RoundedCornersTransformation +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext class MovieDetailFragment : Fragment(R.layout.fragment_movie_preview) { lateinit var poster: ImageView @@ -18,6 +23,9 @@ class MovieDetailFragment : Fragment(R.layout.fragment_movie_preview) { lateinit var popularity: TextView lateinit var releaseDate: TextView + private val context: CoroutineContext = SupervisorJob() + Dispatchers.Main + private val scope = CoroutineScope(context) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) with(view) { @@ -28,28 +36,31 @@ class MovieDetailFragment : Fragment(R.layout.fragment_movie_preview) { releaseDate = findViewById(R.id.releaseDate) } - val movieId = arguments?.getInt(ARG_ID) ?: 550 - val movie = //получаем фильм - poster.load("${BuildConfig.API_IMAGE_BASE_URL}${movie.posterPath}") { - transformations(RoundedCornersTransformation(16f)) + scope.launch { + val movieId = arguments?.getInt(ARG_ID) ?: 550 + val movie: Movie = Api().getMovie(movieId) + 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() { - val manager: FragmentManager = parentFragmentManager - val transaction: FragmentTransaction = manager.beginTransaction() - transaction.replace(R.id.mainFragment, MovieListFragment()) - transaction.commit() - } - }) + activity?.onBackPressedDispatcher?.addCallback( + this.viewLifecycleOwner, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + val manager: FragmentManager = parentFragmentManager + val transaction: FragmentTransaction = manager.beginTransaction() + transaction.replace(R.id.mainFragment, MovieListFragment()) + transaction.commit() + } + }) } companion object { const val TAG = "MovieDetailFragment" const val ARG_ID = "MovieDetailFragment_Arguments_Movie_Id" } -} \ No newline at end of file +} 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..80768f5 100644 --- a/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieListFragment.kt +++ b/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieListFragment.kt @@ -7,16 +7,25 @@ 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.Main + private val scope = CoroutineScope(context) + private val goToDetails = object : MovieItemAdapter.IOnItemClick { override fun onItemClick(movie: Movie) { val manager: FragmentManager = parentFragmentManager val transaction: FragmentTransaction = manager.beginTransaction() val detailsFragment = MovieDetailFragment() - detailsFragment.arguments = Bundle().apply { putInt(MovieDetailFragment.ARG_ID, movie.id) } + detailsFragment.arguments = + Bundle().apply { putInt(MovieDetailFragment.ARG_ID, movie.id) } transaction.replace(R.id.mainFragment, detailsFragment) transaction.commit() } @@ -31,12 +40,15 @@ class MovieListFragment : Fragment(R.layout.fragment_movie_list) { } } val adapter = (recycler.adapter as MovieItemAdapter) - val movies = //получаем фильмы - adapter.submitList(movies) + + scope.launch(Dispatchers.IO) { + adapter.submitList(Api().getMovies().results) + } + super.onViewCreated(view, savedInstanceState) } companion object { const val TAG = "MovieListFragment" } -} \ No newline at end of file +} 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..d330b95 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,56 @@ 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 = "api.themoviedb.org" + private val movies = "3/movie/popular" + private val movie = "3/movie/" + + val ktorLogger = object : Logger { + override fun log(message: String) { + if (message.contains("api_key=")) { + println("${message.split("=").first()}=BuildConfig.API_KEY") + } else { + println(message) + } + } + } + + private val ktorClient = HttpClient() { + install(Logging) { + logger = ktorLogger + level = LogLevel.BODY + } + + install(JsonFeature) { + serializer = KotlinxSerializer(kotlinx.serialization.json.Json { + prettyPrint = true + isLenient = true + ignoreUnknownKeys = true + }) + } + } + + suspend fun getMovie(movieId: Int): Movie = ktorClient.use { + it.get( + host = baseUrl, + path = movie + movieId.toString() + ) { + parameter("api_key", BuildConfig.API_KEY) + } + } -} \ No newline at end of file + suspend fun getMovies(): Movies = ktorClient.use { + it.get( + host = baseUrl, + path = movies + ) { + parameter("api_key", BuildConfig.API_KEY) + } + } +}