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..af8262b 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 @@ -10,9 +10,10 @@ import com.example.epicdatabaseexample.db.person.PersonDao import com.example.epicdatabaseexample.db.person.PersonEntity import com.example.epicdatabaseexample.db.note.NoteDao import com.example.epicdatabaseexample.db.note.NoteEntity +import com.example.epicdatabaseexample.db.note.NoteEntity.Companion.toInt @Database( - version = 2, + version = 3, exportSchema = false, entities = [ NoteEntity::class, @@ -48,7 +49,14 @@ 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 (${NoteEntity.DEFAULT_IS_COMPLETED.toInt()})") + } + } + // TODO Добавить миграцию. ✓ ) 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..5494865 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 @@ -1,5 +1,6 @@ package com.example.epicdatabaseexample.db.note +import android.provider.ContactsContract import androidx.room.* import io.reactivex.Completable import io.reactivex.Maybe @@ -26,7 +27,10 @@ abstract class NoteDao { @Query("DELETE FROM note_table") abstract fun deleteAllNote(): Completable - // TODO Добавить метод для обновления одного элемента. + @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..5da86f5 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,7 +22,10 @@ data class NoteEntity( @ColumnInfo(name = COLUMN_PERSON_NAME) var personName: String = PersonEntity.DEFAULT_PERSON_NAME, - // TODO Нужно добавить нове поле isCompleted типа Boolean. + @ColumnInfo(name = COLUMN_IS_COMPLETED) + var isCompleted: Boolean = DEFAULT_IS_COMPLETED + + // TODO Нужно добавить нове поле isCompleted типа Boolean. ✓ // В SQLite не поддерживается Boolean, но в Room его можно использовать. // Поэтому поле делаем именно Boolean, а при добавлении миграции - нужно будет погуглить, // как обойти это ограничение. @@ -33,5 +36,10 @@ 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" + + const val DEFAULT_IS_COMPLETED = false + + fun Boolean.toInt() = if (this) 1 else 0 } } \ 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..51744df 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,15 @@ 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 -// } + // 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 + } } } \ 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..c3d46f5 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,13 @@ class NoteListViewModel constructor( } fun onIsCompletedClick(note: NoteEntity) { - // TODO Добавить вызов noteDao для обновления элемента в таблице. + // 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..c284833 100644 --- a/HomeWorkNetwork/app/build.gradle +++ b/HomeWorkNetwork/app/build.gradle @@ -9,8 +9,8 @@ android { defaultConfig { - buildConfigField "String", "API_BASE_URL", '"api.themoviedb.org"' - buildConfigField "String", "API_KEY", '"/*ваш ap_key*/"' + buildConfigField "String", "API_BASE_URL", '"https://api.themoviedb.org/"' + buildConfigField "String", "API_KEY", '"ff0974f6deab0b8b609352c7c1f95677"' 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 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2' + implementation "com.squareup.okhttp3:okhttp:4.9.3" + implementation 'com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0' + implementation "io.reactivex.rxjava3:rxjava:3.1.4" + implementation "io.reactivex.rxjava3:rxandroid:3.0.0" + implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0' + implementation 'com.squareup.okhttp3:logging-interceptor:4.8.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..6292408 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: MutableList +) + +@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("release_date") + val releaseDate: String? = null, + @SerialName("original_title") + val originalTitle: String? = null, + @SerialName("popularity") + val popularity: Double? = null +) \ 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..b192925 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 io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.schedulers.Schedulers class MovieDetailFragment : Fragment(R.layout.fragment_movie_preview) { lateinit var poster: ImageView @@ -29,14 +31,25 @@ 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)) - } - originalTitle.text = movie.originalTitle - overview.text = movie.overview - popularity.text = movie.popularity.toString() - releaseDate.text = movie.releaseDate + + ApiObj.getApi().getMovie(movieId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe ( + { movie -> + println("MOVIE OBJECT --> $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 + }, + { + println(it.message) + } + ) activity?.onBackPressedDispatcher?.addCallback(this.viewLifecycleOwner, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { diff --git a/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieItemAdapter.kt b/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieItemAdapter.kt index 64177a5..2ebb998 100644 --- a/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieItemAdapter.kt +++ b/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieItemAdapter.kt @@ -13,7 +13,8 @@ import androidx.recyclerview.widget.RecyclerView import coil.load import coil.transform.RoundedCornersTransformation -internal class MovieItemAdapter : ListAdapter(MOVIE_COMPARATOR) { +internal class +MovieItemAdapter : ListAdapter(MOVIE_COMPARATOR) { private var clickListener: IOnItemClick? = null fun setClickListener(listener: IOnItemClick?) { 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..b3f7a6d 100644 --- a/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieListFragment.kt +++ b/HomeWorkNetwork/app/src/main/java/com/pg/homeworknetwork/MovieListFragment.kt @@ -7,6 +7,13 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.schedulers.Schedulers +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import retrofit2.Retrofit +import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory class MovieListFragment : Fragment(R.layout.fragment_movie_list) { lateinit var recycler: RecyclerView @@ -30,9 +37,20 @@ class MovieListFragment : Fragment(R.layout.fragment_movie_list) { setClickListener(goToDetails) } } - val adapter = (recycler.adapter as MovieItemAdapter) - val movies = //получаем фильмы - adapter.submitList(movies) + + ApiObj.getApi().getMostPopularMovies() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe ( + { movies -> + println("MOVIES OBJECT --> $movies") + val adapter = (recycler.adapter as MovieItemAdapter) + adapter.submitList(movies.results) + }, + { + println(it.message) + } + ) 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..e2ac126 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,46 @@ package com.pg.homeworknetwork -class Api { +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import io.reactivex.rxjava3.core.Single +import kotlinx.serialization.ExperimentalSerializationApi +import okhttp3.MediaType.Companion.toMediaType +import kotlinx.serialization.json.Json +import okhttp3.HttpUrl +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory +import retrofit2.http.GET +import retrofit2.http.Path +@OptIn(ExperimentalSerializationApi::class) + +object ApiObj { + private val contentType = "application/json".toMediaType() + private val json = Json { ignoreUnknownKeys = true } + private val keyInterceptor = Interceptor { chain -> + val url: HttpUrl = chain.request().url.newBuilder().addQueryParameter("api_key", BuildConfig.API_KEY).build() + val request = chain.request().newBuilder().url(url).build() + chain.proceed(request) + } + private val client = OkHttpClient.Builder().apply { + addInterceptor(keyInterceptor) + }.build() + private val retrofit: Retrofit = Retrofit.Builder() + .baseUrl(BuildConfig.API_BASE_URL) + .addConverterFactory(json.asConverterFactory(contentType)) + .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) + .client(client) + .build() + private val api: Api = retrofit.create(Api::class.java) + + fun getApi() : Api = api +} + +interface Api { + @GET("3/movie/{id}") + fun getMovie(@Path("id") id: Int): Single + + @GET("3/movie/popular") + fun getMostPopularMovies(): Single } \ No newline at end of file