Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HW-1 네이버 영화 검색 앱 #510

Merged
merged 54 commits into from
Mar 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
6817584
HW-1 리사이클러뷰 gradle 추가
mtjin Mar 2, 2020
a4d9f43
HW-1 MainActivity UI 구현
mtjin Mar 2, 2020
3bb1185
HW-1 MainActivity UI, RecyclerView 아이디값 추가
mtjin Mar 2, 2020
ca73e51
HW-1 movie item UI 구현 및 styles, colors.xml 값 추가
mtjin Mar 2, 2020
8bf334d
HW-1 retrofit2 라이브러리 추가
mtjin Mar 2, 2020
28953fa
HW-1 모델 추가
mtjin Mar 2, 2020
4ef3b1b
HW-1 Glide dependency 추가
mtjin Mar 2, 2020
86c099b
HW-1 MovieAdapter 추가
mtjin Mar 2, 2020
41c9bac
HW-1 rxjava dependency 추가
mtjin Mar 2, 2020
ed3f290
HW-1 Retrofit 통신 관련 클래스 추가 (ApiClient.kt , ApiInterface.kt)
mtjin Mar 2, 2020
3002b81
HW-1 인터넷 권한 추가
mtjin Mar 2, 2020
1e6a939
HW-1 MainActivity 구현
mtjin Mar 2, 2020
e4ab36d
HW-1 activity_main.xml 에 layoutManager 속성 값 추가
mtjin Mar 2, 2020
8bd02d2
HW-1 item_movie.xml 영화 포스터 높이 수정
mtjin Mar 2, 2020
c7077f0
HW-1 어댑터 클릭리스너 세팅 및 코드정리
mtjin Mar 2, 2020
81cbb1a
HW-1 리사이클러뷰 clipToPadding 속성 추가
mtjin Mar 2, 2020
df70301
HW-1 compilleOptions targetCompatibility 8 추가
mtjin Mar 2, 2020
699bc80
HW-1 인증키 추가
mtjin Mar 2, 2020
61a23dd
HW-1 main.xml 높이 수정
mtjin Mar 2, 2020
9f24cae
HW-1 data class @Expose 어노테이션 추가
mtjin Mar 2, 2020
8b3b686
HW-1 패키지 생성 및 분리
mtjin Mar 2, 2020
4f7037d
HW-1 Interceptor 추가
mtjin Mar 2, 2020
82b7684
HW-1 manifest android:windowSoftInputMode="adjustNothing" 추가
mtjin Mar 2, 2020
82c6f60
HW-1 public -> private 로 함수 접근 제한자 변경
mtjin Mar 2, 2020
8bb5664
HW-1 인증키 수정
mtjin Mar 2, 2020
4eabbe0
HW-1 기본이미지 및 어댑터 placeholder 추가
mtjin Mar 2, 2020
1465352
HW-1 패키지분리
mtjin Mar 2, 2020
d78e16f
HW-1 @EXPOSE 제거
mtjin Mar 3, 2020
1a6c38b
HW-1 사용안하는 import문 제거
mtjin Mar 3, 2020
304729c
HW-1 companion object 중첩 사용 제거, object 클래스로 변경
mtjin Mar 3, 2020
99958df
HW-1 MovieAdapter 클릭리스너 수정 및 불필요한 init 코드 제거
mtjin Mar 3, 2020
a649a84
HW-1 MovieAdapter nullable이 불필요한 코드 수정
mtjin Mar 3, 2020
c0b4c89
HW-1 MovieAdapter rating 예외처리
mtjin Mar 3, 2020
7991155
HW-1 MainActivity 인텐트 예외처리 추가
mtjin Mar 3, 2020
04ed9c0
HW-1 MainActivity 토스트메세지 함수 불필요한 nullable 제거
mtjin Mar 3, 2020
44251a2
HW-1 MovieAdapter 영화제목 HTML을 평문으로 변환하게 수정
mtjin Mar 3, 2020
64031a3
HW-1 rxjava 제거 후 코드 수정
mtjin Mar 3, 2020
aed5aba
Merge branch '2003/mtjin' of https://github.com/StudyFork/GoogryAndro…
mtjin Mar 3, 2020
644d523
HW-1 MainActivty retrofit2 call cancel 코드 추가
mtjin Mar 4, 2020
844e1d6
HW-1 MovieAdapter 변경 가능성 필요없는 프로퍼티 var -> val로 수정 및 Nullable하지 않은 프로퍼…
mtjin Mar 4, 2020
d2d2572
HW-1 MovieAdapter 필요없는 null 검사 제거
mtjin Mar 4, 2020
459bb9e
HW-1 MainActivity retrofit call cancel() 해주는 코드 수정
mtjin Mar 4, 2020
c6cdef9
HW-1 MovieAdapter toMutableList() 비효율적인 코드 제거
mtjin Mar 4, 2020
24796da
HW-1 MovieAdapter 아이템추가 addAll()로 수정
mtjin Mar 4, 2020
f4cd558
HW-1 MovieAdapter items var->val 수정
mtjin Mar 4, 2020
207092d
HW-1 MainActivity onDestory()에 retrofit call cancel 추가
mtjin Mar 4, 2020
cf952ff
HW-1 MainActivity cancel() -> safe call 추가
mtjin Mar 4, 2020
fb3830f
HW-1 MainActivity lateinit 변수 초기화 여부 검사
mtjin Mar 4, 2020
87f2a2e
HW-1 AppInterceptor Builder pattern 이므로 run 제거
mtjin Mar 4, 2020
f82dc00
HW-1 액티비티가 아닌 어댑터에서 notify 해주게 수정
mtjin Mar 4, 2020
35d017a
HW-1 Glide 개행 수정
mtjin Mar 4, 2020
254cb98
HW-1 initView() private 접근 제한자 추가
mtjin Mar 4, 2020
e0b3a4a
HW-1 retrofit onResponse() 필요없는 로직 수정
mtjin Mar 4, 2020
15bb680
HW-1 어댑터 아이템 클릭리스너 로직 변경 (interface -> higher-order function)
mtjin Mar 4, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions 2003/mtjin/AndroidArchitectureStudy/app/build.gradle
Expand Up @@ -21,6 +21,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
targetCompatibility = "8"
sourceCompatibility = "8"
}
}

dependencies {
Expand All @@ -32,4 +36,17 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
//recyclerview
implementation 'androidx.recyclerview:recyclerview:1.1.0'
//retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
implementation 'com.squareup.retrofit2:converter-gson:2.6.2'
//glide
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
//rxjava
implementation "io.reactivex.rxjava2:rxjava:2.2.12"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'

}
@@ -1,15 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.mtjin.androidarchitecturestudy">

<uses-permission android:name="android.permission.INTERNET"/>

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".ui.MainActivity"
android:windowSoftInputMode="adjustNothing">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down

This file was deleted.

@@ -0,0 +1,24 @@
package com.mtjin.androidarchitecturestudy.api

import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object ApiClient {
private const val BASE_URL = "https://openapi.naver.com/"
fun getApiClient(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(provideOkHttpClient(AppInterceptor()))
.addConverterFactory(GsonConverterFactory.create())
.build()
}

private fun provideOkHttpClient(
interceptor: AppInterceptor
): OkHttpClient = OkHttpClient.Builder()
.run {
addInterceptor(interceptor)
build()
}
}
@@ -0,0 +1,13 @@
package com.mtjin.androidarchitecturestudy.api

import com.mtjin.androidarchitecturestudy.data.MovieResponse
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query

interface ApiInterface {
@GET("v1/search/movie.json")
fun getSearchMovie(
@Query("query") query: String
): Call<MovieResponse>
}
@@ -0,0 +1,18 @@
package com.mtjin.androidarchitecturestudy.api

import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException

class AppInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain)
: Response = with(chain) {
val newRequest = request().newBuilder()
.addHeader("X-Naver-Client-Id", "33chRuAiqlSn5hn8tIme")
.addHeader("X-Naver-Client-Secret", "fyfwt9PCUN")
.build()

proceed(newRequest)
}
}
@@ -0,0 +1,23 @@
package com.mtjin.androidarchitecturestudy.data


import com.google.gson.annotations.SerializedName

data class Movie(
@SerializedName("actor")
val actor: String,
@SerializedName("director")
val director: String,
@SerializedName("image")
val image: String,
@SerializedName("link")
val link: String,
@SerializedName("pubDate")
val pubDate: String,
@SerializedName("subtitle")
val subtitle: String,
@SerializedName("title")
val title: String,
@SerializedName("userRating")
val userRating: String
)
@@ -0,0 +1,17 @@
package com.mtjin.androidarchitecturestudy.data


import com.google.gson.annotations.SerializedName

data class MovieResponse(
@SerializedName("display")
val display: Int,
@SerializedName("items")
val movies: List<Movie>,
@SerializedName("lastBuildDate")
val lastBuildDate: String,
@SerializedName("start")
val start: Int,
@SerializedName("total")
val total: Int
)
@@ -0,0 +1,100 @@
package com.mtjin.androidarchitecturestudy.ui

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.mtjin.androidarchitecturestudy.R
import com.mtjin.androidarchitecturestudy.api.ApiClient
import com.mtjin.androidarchitecturestudy.api.ApiInterface
import com.mtjin.androidarchitecturestudy.data.Movie
import com.mtjin.androidarchitecturestudy.data.MovieResponse
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response


class MainActivity : AppCompatActivity() {

private lateinit var etInput: EditText
private lateinit var btnSearch: Button
private lateinit var rvMovies: RecyclerView
private lateinit var movieAdapter: MovieAdapter
private lateinit var movieCall: Call<MovieResponse>

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

initView()
initListener()
}

private fun initView() {
etInput = findViewById(R.id.et_input)
btnSearch = findViewById(R.id.btn_search)
rvMovies = findViewById(R.id.rv_movies)
movieAdapter = MovieAdapter()
rvMovies.adapter = movieAdapter
}

private fun initListener() {
//어댑터 아이템 클릭리스너
movieAdapter.setItemClickListener { movie ->
Intent(Intent.ACTION_VIEW, Uri.parse(movie.link)).takeIf {
it.resolveActivity(packageManager) != null
}?.run(this::startActivity)
}

//검색버튼
btnSearch.setOnClickListener {
val query = etInput.text.toString().trim()
if (query.isEmpty()) {
onToastMessage("검색어를 입력해주세요.")
} else {
onToastMessage("잠시만 기다려주세요.")
requestMovie(query)
}
}
}

private fun requestMovie(query: String) {
movieAdapter.clear()
val apiInterface = ApiClient.getApiClient().create(ApiInterface::class.java)
movieCall = apiInterface.getSearchMovie(query)
movieCall.enqueue(object : Callback<MovieResponse> {
override fun onFailure(call: Call<MovieResponse>, t: Throwable) {
onToastMessage("불러오는데 실패 했습니다.")
}

override fun onResponse(
call: Call<MovieResponse>,
response: Response<MovieResponse>
) {
with(response) {
val body = body()
if (isSuccessful && body != null) {
body.movies.let { movieAdapter.setItems(it) }
} else {
onToastMessage("불러오는데 실패 했습니다.")
}
}
}
})
}

private fun onToastMessage(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}

override fun onDestroy() {
super.onDestroy()
if (this::movieCall.isInitialized) {
movieCall.cancel()
}
}
}
@@ -0,0 +1,74 @@
package com.mtjin.androidarchitecturestudy.ui

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RatingBar
import android.widget.TextView
import androidx.core.text.HtmlCompat
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.mtjin.androidarchitecturestudy.R
import com.mtjin.androidarchitecturestudy.data.Movie

class MovieAdapter :
RecyclerView.Adapter<MovieAdapter.ViewHolder>() {
private lateinit var callback: (Movie) -> Unit
private val items: ArrayList<Movie> = ArrayList()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view: View = LayoutInflater.from(parent.context)
.inflate(R.layout.item_movie, parent, false)
val viewHolder = ViewHolder(view)
view.setOnClickListener {
callback(items[viewHolder.adapterPosition])
}
return viewHolder
}

override fun getItemCount(): Int = items.size

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
items[position].let {
holder.bind(it)
}
}

class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

private val ivPoster = itemView.findViewById<ImageView>(R.id.iv_poster)
private val rvRating = itemView.findViewById<RatingBar>(R.id.rb_rating)
private val tvTitle = itemView.findViewById<TextView>(R.id.tv_title)
private val tvReleaseDate = itemView.findViewById<TextView>(R.id.tv_release_date)
private val tvActor = itemView.findViewById<TextView>(R.id.tv_actor)
private val tvDirector = itemView.findViewById<TextView>(R.id.tv_director)

fun bind(movie: Movie) {
with(movie) {
Glide.with(itemView).load(image)
.placeholder(R.drawable.ic_default)
.into(ivPoster!!)
rvRating.rating = (userRating.toFloatOrNull() ?: 0f) / 2
tvTitle.text = HtmlCompat.fromHtml(title, HtmlCompat.FROM_HTML_MODE_COMPACT)
tvReleaseDate.text = pubDate
tvActor.text = actor
tvDirector.text = director
}
}
}

fun setItems(items: List<Movie>) {
this.items.addAll(items)
notifyDataSetChanged()
}

fun setItemClickListener(callback: (Movie) -> Unit) {
this.callback = callback
}

fun clear() {
this.items.clear()
notifyDataSetChanged()
}
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.