diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 915add9..2490f07 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,7 +2,8 @@
-
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByDictionary.java b/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByDictionary.java
new file mode 100644
index 0000000..cf45a2f
--- /dev/null
+++ b/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByDictionary.java
@@ -0,0 +1,22 @@
+package com.example.yeseul.movieapp.data.model;
+
+import android.os.Build;
+
+import com.example.yeseul.movieapp.pojo.Movie;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class MovieSortByDictionary implements MovieSortStrategy {
+ @Override
+ public List sort(List list) {
+ if(Build.VERSION_CODES.N <= Build.VERSION.SDK_INT) {
+ list = list.stream().sorted(Comparator.comparing(Movie::getTitle)).collect(Collectors.toList());
+ } else {
+ Collections.sort(list, (o1, o2) -> (o1.getTitle().compareTo(o2.getTitle())));
+ }
+ return list;
+ }
+}
diff --git a/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByHighestRating.java b/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByHighestRating.java
new file mode 100644
index 0000000..3a7295c
--- /dev/null
+++ b/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByHighestRating.java
@@ -0,0 +1,24 @@
+package com.example.yeseul.movieapp.data.model;
+
+import android.os.Build;
+
+import com.example.yeseul.movieapp.pojo.Movie;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class MovieSortByHighestRating implements MovieSortStrategy {
+ @Override
+ public List sort(List list) {
+ if(Build.VERSION_CODES.N <= Build.VERSION.SDK_INT) {
+ list = list.stream().
+ sorted((o1, o2) -> Double.compare(Double.parseDouble(o2.getUserRating()), Double.parseDouble(o1.getUserRating()))).
+ collect(Collectors.toList());
+ } else {
+ Collections.sort(list, (o1, o2) -> (Double.compare(Double.parseDouble(o2.getUserRating()), Double.parseDouble(o1.getUserRating()))));
+ }
+ return list;
+ }
+}
diff --git a/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByLatestPubDate.java b/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByLatestPubDate.java
new file mode 100644
index 0000000..1b19af8
--- /dev/null
+++ b/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByLatestPubDate.java
@@ -0,0 +1,22 @@
+package com.example.yeseul.movieapp.data.model;
+
+import android.os.Build;
+
+import com.example.yeseul.movieapp.pojo.Movie;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class MovieSortByLatestPubDate implements MovieSortStrategy {
+ @Override
+ public List sort(List list) {
+ if(Build.VERSION_CODES.N <= Build.VERSION.SDK_INT) {
+ list = list.stream().sorted(Comparator.comparing(Movie::getPubDate).reversed()).collect(Collectors.toList());
+ } else {
+ Collections.sort(list, (o1, o2) -> (o2.getPubDate().compareTo(o1.getPubDate())));
+ }
+ return list;
+ }
+}
diff --git a/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByLowestRating.java b/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByLowestRating.java
new file mode 100644
index 0000000..711cc6d
--- /dev/null
+++ b/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByLowestRating.java
@@ -0,0 +1,24 @@
+package com.example.yeseul.movieapp.data.model;
+
+import android.os.Build;
+
+import com.example.yeseul.movieapp.pojo.Movie;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class MovieSortByLowestRating implements MovieSortStrategy {
+ @Override
+ public List sort(List list) {
+ if(Build.VERSION_CODES.N <= Build.VERSION.SDK_INT) {
+ list = list.stream().
+ sorted((o1, o2) -> Double.compare(Double.parseDouble(o1.getUserRating()), Double.parseDouble(o2.getUserRating()))).
+ collect(Collectors.toList());
+ } else {
+ Collections.sort(list, (o1, o2) -> (Double.compare(Double.parseDouble(o1.getUserRating()), Double.parseDouble(o2.getUserRating()))));
+ }
+ return list;
+ }
+}
diff --git a/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByOldPubDate.java b/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByOldPubDate.java
new file mode 100644
index 0000000..dce80cb
--- /dev/null
+++ b/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortByOldPubDate.java
@@ -0,0 +1,24 @@
+package com.example.yeseul.movieapp.data.model;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import com.example.yeseul.movieapp.pojo.Movie;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class MovieSortByOldPubDate implements MovieSortStrategy {
+ @Override
+ public List sort(List list) {
+ if(Build.VERSION_CODES.N <= Build.VERSION.SDK_INT) {
+ list = list.stream().sorted(Comparator.comparing(Movie::getPubDate)).collect(Collectors.toList());
+ } else {
+ Collections.sort(list, (o1, o2) -> (o2.getPubDate().compareTo(o1.getPubDate())));
+ }
+ return list;
+ }
+}
diff --git a/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortStrategy.java b/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortStrategy.java
new file mode 100644
index 0000000..788c661
--- /dev/null
+++ b/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieSortStrategy.java
@@ -0,0 +1,13 @@
+package com.example.yeseul.movieapp.data.model;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import com.example.yeseul.movieapp.pojo.Movie;
+
+import java.util.List;
+
+public interface MovieSortStrategy {
+
+ List sort(List list);
+}
diff --git a/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieStrategyFactory.java b/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieStrategyFactory.java
new file mode 100644
index 0000000..d4de68d
--- /dev/null
+++ b/app/src/main/java/com/example/yeseul/movieapp/data/model/MovieStrategyFactory.java
@@ -0,0 +1,41 @@
+package com.example.yeseul.movieapp.data.model;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import static com.example.yeseul.movieapp.data.model.MovieSortStrategy.*;
+
+public class MovieStrategyFactory {
+
+ // Interface 에 있던 상수들을 Factory Class 로 이동
+ // 넘어오는 매개변수가 정확한 타입인지 인증하기 위해 IntDef 사용
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({DICTIONARY_ORDER, LATEST_ORDER, OLDEST_ORDER, HIGHEST_RATING_ORDER, LOWEST_RATING_ORDER})
+
+ public @interface SortTypeDef {}
+
+ public static final int DICTIONARY_ORDER = 0;
+ public static final int LATEST_ORDER = 1;
+ public static final int OLDEST_ORDER = 2;
+ public static final int HIGHEST_RATING_ORDER = 3;
+ public static final int LOWEST_RATING_ORDER = 4;
+
+ public static MovieSortStrategy create(int idx) {
+
+ switch (idx) {
+ case DICTIONARY_ORDER:
+ return new MovieSortByDictionary();
+ case LATEST_ORDER:
+ return new MovieSortByLatestPubDate();
+ case OLDEST_ORDER:
+ return new MovieSortByOldPubDate();
+ case HIGHEST_RATING_ORDER:
+ return new MovieSortByHighestRating();
+ case LOWEST_RATING_ORDER:
+ return new MovieSortByLowestRating();
+ }
+ return null;
+ }
+}
diff --git a/app/src/main/java/com/example/yeseul/movieapp/data/remote/RetrofitClient.java b/app/src/main/java/com/example/yeseul/movieapp/data/remote/RetrofitClient.java
index 73b3401..a360831 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/data/remote/RetrofitClient.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/data/remote/RetrofitClient.java
@@ -15,7 +15,6 @@
public class RetrofitClient {
private static final String BASE_URL = "https://openapi.naver.com/v1/";
-
private static final String CLIENT_ID = "uMJDm4Jz6qzLyQPT5KK9";
private static final String CLIENT_SECRET = "DxWswCSZ20";
diff --git a/app/src/main/java/com/example/yeseul/movieapp/data/source/movie/MovieDataSource.java b/app/src/main/java/com/example/yeseul/movieapp/data/source/movie/MovieDataSource.java
index c396ff7..01366ce 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/data/source/movie/MovieDataSource.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/data/source/movie/MovieDataSource.java
@@ -8,7 +8,10 @@
import io.reactivex.Single;
+/*
+ * Repository 에서 사용할 기능 선언 */
public interface MovieDataSource {
+ /* API 가 유일한 데이터를 반환하는 경우 Single 이 유용하다.(한 번만 데이터를 발행) */
Single searchMovies(Map request);
}
diff --git a/app/src/main/java/com/example/yeseul/movieapp/data/source/movie/MovieRemoteDataSource.java b/app/src/main/java/com/example/yeseul/movieapp/data/source/movie/MovieRemoteDataSource.java
index eb99220..b07eb07 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/data/source/movie/MovieRemoteDataSource.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/data/source/movie/MovieRemoteDataSource.java
@@ -11,6 +11,9 @@
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
+/*
+ * Remote Data (인터넷에서 가져오는 데이터들)
+ * Singleton Pattern 으로 작성 -> presenter 에 등록할 때 getInstance 사용 */
public class MovieRemoteDataSource implements MovieDataSource{
private static MovieRemoteDataSource movieRemoteDataSource = null;
diff --git a/app/src/main/java/com/example/yeseul/movieapp/data/source/movie/MovieRepository.java b/app/src/main/java/com/example/yeseul/movieapp/data/source/movie/MovieRepository.java
index aad2a7c..116faa8 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/data/source/movie/MovieRepository.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/data/source/movie/MovieRepository.java
@@ -8,6 +8,8 @@
import io.reactivex.Single;
+/*
+ * Presenter 에서 Repository 에 접근할 때 사용하는 ref */
public class MovieRepository implements MovieDataSource {
private static MovieRepository movieRepository = null;
diff --git a/app/src/main/java/com/example/yeseul/movieapp/mapper/MovieMapper.java b/app/src/main/java/com/example/yeseul/movieapp/mapper/MovieMapper.java
index c8cd8fe..3fa4e20 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/mapper/MovieMapper.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/mapper/MovieMapper.java
@@ -3,12 +3,17 @@
import java.util.HashMap;
import java.util.Map;
+/*
+ * */
public class MovieMapper {
- public static final String QUERY_STRING_SEARCH_KEY = "query";
- public static final String QUERY_STRING_PAGE_UNIT = "display";
- public static final String QUERY_STRING_START = "start";
+ /* private 로 접근 제한자 수정 */
+ private static final String QUERY_STRING_SEARCH_KEY = "query";
+ private static final String QUERY_STRING_PAGE_UNIT = "display";
+ private static final String QUERY_STRING_START = "start";
+ /*
+ * Retrofit 을 이용해 보낼 Query 생성 */
public static Map toRequest(String searchKey, int pageUnit, int start){
Map queryMap = new HashMap<>();
diff --git a/app/src/main/java/com/example/yeseul/movieapp/pojo/Movie.java b/app/src/main/java/com/example/yeseul/movieapp/pojo/Movie.java
index 003167c..5c4b54d 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/pojo/Movie.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/pojo/Movie.java
@@ -5,6 +5,12 @@
public class Movie {
+ /*
+ * SerializedName : Gson 을 이용해 Json 에서 Object 로 객체를 변경시킬 때 Json 에 등록된 이름과 연결지어 준다.
+ * Expose : Expose Annotation 이 존재하면 Gson 을 이용할 때 해당 변수는 노출시키지 않게 됨
+ *
+ * */
+
@SerializedName("title")
@Expose
private String title;
diff --git a/app/src/main/java/com/example/yeseul/movieapp/utils/BindingUtil.java b/app/src/main/java/com/example/yeseul/movieapp/utils/BindingUtil.java
index 6d75fd8..7731a9f 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/utils/BindingUtil.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/utils/BindingUtil.java
@@ -20,6 +20,8 @@ public static int setVisibility(boolean isVisible){
return isVisible ? View.VISIBLE : View.GONE;
}
+ /*
+ * holder_movie_item.xml 에서 rating 과 결합. item 이 변경되면 그에 맞게 data change*/
@BindingAdapter({"rating"})
public static void setRating(RatingBar ratingBar, String userRating){
@@ -29,6 +31,8 @@ public static void setRating(RatingBar ratingBar, String userRating){
ratingBar.setRating((float) rating / 2);
}
+ /*
+ * holder_movie_item.xml 의 HTML Text 들과 결합. */
@BindingAdapter({"boldText"})
public static void setBoldText(TextView textView, String text){
diff --git a/app/src/main/java/com/example/yeseul/movieapp/utils/GlideUtil.java b/app/src/main/java/com/example/yeseul/movieapp/utils/GlideUtil.java
index e45fd90..043f535 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/utils/GlideUtil.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/utils/GlideUtil.java
@@ -15,6 +15,7 @@ public static void loadImage(ImageView imageView, String url){
GlideApp.with(imageView).load(url).into(imageView);
}
+ /* URL 이 변경되면 해당 Image 를 불러오도록 설정 */
@BindingAdapter({"imageCenterCrop"})
public static void loadImageCenterCrop(ImageView imageView, String url){
diff --git a/app/src/main/java/com/example/yeseul/movieapp/utils/KeyboardUtil.java b/app/src/main/java/com/example/yeseul/movieapp/utils/KeyboardUtil.java
index 25a7492..09fdde1 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/utils/KeyboardUtil.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/utils/KeyboardUtil.java
@@ -4,6 +4,9 @@
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
+
+/*
+ * Keyboard Open, Close Command Handle */
public class KeyboardUtil {
public static void closeKeyboard(Context context, EditText editText){
diff --git a/app/src/main/java/com/example/yeseul/movieapp/utils/NetworkStatusCheckUtil.java b/app/src/main/java/com/example/yeseul/movieapp/utils/NetworkStatusCheckUtil.java
new file mode 100644
index 0000000..fa8e5f6
--- /dev/null
+++ b/app/src/main/java/com/example/yeseul/movieapp/utils/NetworkStatusCheckUtil.java
@@ -0,0 +1,20 @@
+package com.example.yeseul.movieapp.utils;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+public class NetworkStatusCheckUtil {
+
+ public static final String NETWORK_STATUS_CHANGE_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
+
+ public static boolean checkCurrentNetworkStatus(final Context context) {
+ final ConnectivityManager connectivityManager =
+ (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ final NetworkInfo currentNetworkStatus = connectivityManager.getActiveNetworkInfo();
+
+ // 연결 상태 반환
+ return currentNetworkStatus != null && currentNetworkStatus.isConnected();
+ }
+}
diff --git a/app/src/main/java/com/example/yeseul/movieapp/view/BaseActivity.java b/app/src/main/java/com/example/yeseul/movieapp/view/BaseActivity.java
index 31e044b..9c9dfa8 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/view/BaseActivity.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/view/BaseActivity.java
@@ -9,7 +9,14 @@
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
+/*
+* Activity 들이 가지는 공통의 설정
+* DataBinding 을 이용한 View-Presenter 사이의 의존성을 약화
+* MVP 패턴을 사용하므로 Presenter 도 동일한 설정 필요
+ */
public abstract class BaseActivity extends AppCompatActivity {
+
+ // Log 확인을 위한 TAG 설정. 기본적으로 클래스 이름으로 구분
protected final String TAG = getClass().getSimpleName();
protected B binding;
@@ -22,7 +29,9 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
// 화면 세로 고정
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ // View 와 바인딩하기 위한 객체
binding = DataBindingUtil.setContentView(this, getLayoutId());
+ // Presenter 를 이용한 이벤트 위임
presenter = getPresenter();
}
diff --git a/app/src/main/java/com/example/yeseul/movieapp/view/BasePresenter.java b/app/src/main/java/com/example/yeseul/movieapp/view/BasePresenter.java
index a7ac772..9261559 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/view/BasePresenter.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/view/BasePresenter.java
@@ -1,8 +1,12 @@
package com.example.yeseul.movieapp.view;
+/*
+ * 모든 Presenter 가 공통으로 가져야하는 함수들 선언 */
public interface BasePresenter {
- void onViewCreated();
+ void onViewAttached();
+
+ void onViewDetached();
void loadItems(boolean isRefresh);
}
diff --git a/app/src/main/java/com/example/yeseul/movieapp/view/BaseView.java b/app/src/main/java/com/example/yeseul/movieapp/view/BaseView.java
index f6c6504..2d33871 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/view/BaseView.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/view/BaseView.java
@@ -1,5 +1,7 @@
package com.example.yeseul.movieapp.view;
+/*
+ * 모든 View 가 공통적으로 가져야하는 함수들 선언 */
public interface BaseView {
void makeToast(String text);
diff --git a/app/src/main/java/com/example/yeseul/movieapp/view/adapter/AdapterContract.java b/app/src/main/java/com/example/yeseul/movieapp/view/adapter/AdapterContract.java
index b06cd18..8784db3 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/view/adapter/AdapterContract.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/view/adapter/AdapterContract.java
@@ -2,13 +2,17 @@
import java.util.List;
+/*
+ * Google Architecture 에서 추천한 Adapter Interface 구조 */
public interface AdapterContract {
+ /* Adapter 가 달린 View 에서 발생하는 이벤트들만 가짐 */
interface View {
void setOnItemClickListener(OnItemClickListener itemClickListener);
}
+ /* Adapter 가 가진 Data 를 조작하는 함수들만 가짐 */
interface Model {
T getItem(int position);
diff --git a/app/src/main/java/com/example/yeseul/movieapp/view/adapter/BaseRecyclerAdapter.java b/app/src/main/java/com/example/yeseul/movieapp/view/adapter/BaseRecyclerAdapter.java
index b715222..d8abd81 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/view/adapter/BaseRecyclerAdapter.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/view/adapter/BaseRecyclerAdapter.java
@@ -7,7 +7,7 @@
import java.util.ArrayList;
import java.util.List;
-public abstract class BaseRecyclerAdapter extends RecyclerView.Adapter implements AdapterContract.View, AdapterContract.Model{
+public abstract class BaseRecyclerAdapter extends RecyclerView.Adapter implements AdapterContract.View, AdapterContract.Model{
protected List itemList;
protected OnItemClickListener onItemClickListener;
@@ -31,6 +31,7 @@ public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
+
@Override
public long getItemId(int position) {
return position;
@@ -196,5 +197,6 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int
onBindView((H) holder, position);
}
+ // 각 ViewHolder 에 맞게 Item 을 설정해주는 역할
protected abstract void onBindView(H holder, int position);
}
diff --git a/app/src/main/java/com/example/yeseul/movieapp/view/adapter/OnItemClickListener.java b/app/src/main/java/com/example/yeseul/movieapp/view/adapter/OnItemClickListener.java
index 3077fee..df23324 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/view/adapter/OnItemClickListener.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/view/adapter/OnItemClickListener.java
@@ -1,5 +1,7 @@
package com.example.yeseul.movieapp.view.adapter;
+/*
+ * RecyclerView ItemClick Event 정의 */
public interface OnItemClickListener {
void onItemClick(int position);
diff --git a/app/src/main/java/com/example/yeseul/movieapp/view/main/MainActivity.java b/app/src/main/java/com/example/yeseul/movieapp/view/main/MainActivity.java
index 574b81f..9b2a75b 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/view/main/MainActivity.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/view/main/MainActivity.java
@@ -1,5 +1,10 @@
package com.example.yeseul.movieapp.view.main;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
@@ -12,15 +17,32 @@
import android.view.inputmethod.EditorInfo;
import com.example.yeseul.movieapp.R;
+import com.example.yeseul.movieapp.data.model.MovieSortByDictionary;
+import com.example.yeseul.movieapp.data.model.MovieSortStrategy;
+import com.example.yeseul.movieapp.data.model.MovieStrategyFactory;
import com.example.yeseul.movieapp.data.source.movie.MovieRepository;
import com.example.yeseul.movieapp.databinding.ActivityMovieBinding;
import com.example.yeseul.movieapp.utils.KeyboardUtil;
+import com.example.yeseul.movieapp.utils.NetworkStatusCheckUtil;
import com.example.yeseul.movieapp.view.BaseActivity;
+
public class MainActivity extends BaseActivity implements MainContract.View {
private MovieListAdapter adapter;
+ private BroadcastReceiver mNetworkStatusReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Log.e("Test", intent.getAction() + " state : " + NetworkStatusCheckUtil.checkCurrentNetworkStatus(MainActivity.this));
+ // 네트워크 상태 변경 Action 이 발생한 경우
+ if(intent.getAction().equals(NetworkStatusCheckUtil.NETWORK_STATUS_CHANGE_ACTION)) {
+ // 현재 인터넷 상태를 설정
+ presenter.setNetworkStatus(NetworkStatusCheckUtil.checkCurrentNetworkStatus(MainActivity.this));
+ }
+ }
+ };
+
@Override
protected int getLayoutId() {
return R.layout.activity_movie;
@@ -28,6 +50,8 @@ protected int getLayoutId() {
@Override
protected MainPresenter getPresenter() {
+ // Presenter 에서 사용할 Repository 객체는 싱글턴으로 작성
+ // BaseActivity 에서 Presenter 는 미리 설정
return new MainPresenter(this, MovieRepository.getInstance());
}
@@ -35,15 +59,47 @@ protected MainPresenter getPresenter() {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ // RecyclerViewAdapter 설정
adapter = new MovieListAdapter(this);
+
+ // Presenter 에서 View 를 거치는 과정을 생략하기 위해
+ // Adapter 를 Presenter 에 등록
presenter.setAdapterView(adapter);
presenter.setAdapterModel(adapter);
+ // xml 에서 변수로 설정해놓은 presenter 설정
binding.setPresenter(presenter);
initView();
+ }
- presenter.onViewCreated();
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // callback 형태로 View 가 설정된 후 호출
+ presenter.onViewAttached();
+ registerNetworkReceiver();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ // view 의 사용이 끝난 후
+ presenter.onViewDetached();
+ unregisterNetworkReceiver();
+ }
+
+ /*
+ * receiver 등록하기 */
+ private void registerNetworkReceiver() {
+ IntentFilter intentFilter = new IntentFilter(NetworkStatusCheckUtil.NETWORK_STATUS_CHANGE_ACTION);
+ registerReceiver(mNetworkStatusReceiver, intentFilter);
+ }
+
+ /*
+ * receiver 제거하기 */
+ private void unregisterNetworkReceiver() {
+ unregisterReceiver(mNetworkStatusReceiver);
}
private void initView() {
@@ -56,7 +112,7 @@ private void initView() {
binding.recyclerMovie.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
// 최하단 스크롤 감지
- binding.recyclerMovie.setOnScrollListener(new RecyclerView.OnScrollListener(){
+ binding.recyclerMovie.addOnScrollListener(new RecyclerView.OnScrollListener(){
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
@@ -67,7 +123,8 @@ public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newStat
});
// 키보드 검색 버튼 리스너 등록
- binding.searchBox.etSearch.setOnEditorActionListener((v, actionId, event) -> {
+ binding.searchBox.etSearch.setOnEditorActionListener( (v, actionId, event) -> {
+ // IME_ACTION_SEARCH 와 View 에 Option 을 같게 설정해줘야 함
if(actionId == EditorInfo.IME_ACTION_SEARCH){
onSearchButtonClicked();
return true;
@@ -77,8 +134,25 @@ public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newStat
// 검색 버튼 리스너 등록
binding.searchBox.btnSubmit.setOnClickListener(v -> onSearchButtonClicked());
+
+ // 정렬 버튼 리스너 등록
+ binding.searchBox.btnOrder.setOnClickListener(v ->onOrderSelectButtonClicked());
}
+ private void onOrderSelectButtonClicked() {
+
+ final String[] items = getResources().getStringArray(R.array.search_order);
+
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
+ dialogBuilder.setTitle(getString(R.string.order_title))
+ .setItems(items, (di, i) -> {
+ binding.searchBox.btnOrder.setText(items[i]);
+ presenter.onOrderSelectionChanged(MovieStrategyFactory.create(i));
+ di.dismiss();
+ });
+
+ dialogBuilder.show();
+ }
/**
* 키보드 검색 혹은 검색 버튼 눌렀을 때 호출 */
private void onSearchButtonClicked(){
@@ -87,9 +161,13 @@ private void onSearchButtonClicked(){
String searchKey = binding.searchBox.etSearch.getText().toString();
if(!TextUtils.isEmpty(searchKey)) {
+ // Event 를 Presenter 에 위임
presenter.onSearchButtonClicked(searchKey);
+ // RecyclerView 위치를 최상단으로 설정
binding.recyclerMovie.scrollToPosition(0);
+ // View 값 설정
binding.emptyView.setText("");
+ // 키보드 닫기
KeyboardUtil.closeKeyboard(this, binding.searchBox.etSearch);
} else {
@@ -112,6 +190,7 @@ public void onSearchResultEmpty(String searchKey) {
@Override
public void startMovieDetailPage(String linkUrl) {
+ // 다음 탭으로 넘어가기
CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder()
.setToolbarColor(getResources().getColor(R.color.colorPrimary))
.build();
diff --git a/app/src/main/java/com/example/yeseul/movieapp/view/main/MainContract.java b/app/src/main/java/com/example/yeseul/movieapp/view/main/MainContract.java
index ac42353..621eb2c 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/view/main/MainContract.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/view/main/MainContract.java
@@ -1,25 +1,43 @@
package com.example.yeseul.movieapp.view.main;
+import com.example.yeseul.movieapp.data.model.MovieSortStrategy;
import com.example.yeseul.movieapp.pojo.Movie;
import com.example.yeseul.movieapp.view.BasePresenter;
import com.example.yeseul.movieapp.view.BaseView;
import com.example.yeseul.movieapp.view.adapter.AdapterContract;
+/**
+ * View - Presenter 는 1:1 관계
+ * 각 객체의 공통 기능 설정
+ */
public interface MainContract {
interface View extends BaseView {
+ /*
+ * 검색 결과가 없을 때*/
void onSearchResultEmpty(String searchKey);
+ /*
+ * 영화를 선택했을 때 다음 화면으로 넘어가는 기능 */
void startMovieDetailPage(String linkUrl);
}
interface Presenter extends BasePresenter {
+ /* View 에서 발생하는 이벤트 처리는 Presenter 에 위임*/
void onSearchButtonClicked(String searchKey);
+ /* Presenter 에서 Adapter 를 가지고 View 갱신함 */
void setAdapterView(AdapterContract.View adapterView);
+ /* Presenter 에서 Adapter 의 데이터를 조작함 */
void setAdapterModel(AdapterContract.Model adapterModel);
+
+ /* Network 상태를 Presenter 의 Observer 를 이용하여 관찰 */
+ void setNetworkStatus(boolean currentStatus);
+
+ /* 정렬 상태가 변경되었을 경우 */
+ void onOrderSelectionChanged(MovieSortStrategy sortMethod);
}
}
diff --git a/app/src/main/java/com/example/yeseul/movieapp/view/main/MainPresenter.java b/app/src/main/java/com/example/yeseul/movieapp/view/main/MainPresenter.java
index b57c015..8c1a447 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/view/main/MainPresenter.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/view/main/MainPresenter.java
@@ -3,10 +3,13 @@
import android.annotation.SuppressLint;
import android.databinding.ObservableBoolean;
import android.databinding.ObservableField;
+import android.util.Log;
+import com.example.yeseul.movieapp.data.model.MovieSortStrategy;
import com.example.yeseul.movieapp.data.source.movie.MovieRepository;
import com.example.yeseul.movieapp.mapper.MovieMapper;
import com.example.yeseul.movieapp.pojo.Movie;
+import com.example.yeseul.movieapp.utils.NetworkStatusCheckUtil;
import com.example.yeseul.movieapp.view.adapter.AdapterContract;
import java.util.List;
@@ -27,16 +30,31 @@ public class MainPresenter implements MainContract.Presenter {
private int currentPage = 0; // 현재 페이지 index
private boolean isEndOfPage = false; // 페이지 끝 flag
+ private boolean isNetworkConnected = false;
+
public MainPresenter(MainContract.View view, MovieRepository repository) {
this.view = view;
this.repository = repository;
}
+ /* BasePresenter */
+ @Override
+ public void onViewAttached() {
+ // 네트워크 초기 상태 설정
+ isNetworkConnected = NetworkStatusCheckUtil.checkCurrentNetworkStatus((MainActivity)view);
+ }
+
+ @Override
+ public void onViewDetached() {
+ // do Something
+ }
+
@Override
- public void onViewCreated() {
- // do nothing
+ public void setNetworkStatus(boolean currentStatus) {
+ this.isNetworkConnected = currentStatus;
}
+ /* BasePresenter */
@Override
public void loadItems(boolean isRefresh) {
@@ -47,18 +65,26 @@ public void loadItems(boolean isRefresh) {
adapterModel.clearItems();
}
+ // 인터넷 연결이 안되어있는 경우
+ if(!isNetworkConnected) {
+ view.makeToast("인터넷 상태를 확인해 주세요 !");
+ return;
+ }
+
// 마지막 페이지가 아니고 로딩중 아닌 경우 getMovieList 호출
if(!isLoading.get() && !isEndOfPage) {
getMovieList();
}
}
+ /* MainContract.Presenter */
@Override
public void onSearchButtonClicked(String searchKey) {
this.searchKey = searchKey;
loadItems(true);
}
+ /* MainContract.Presenter */
@Override
public void setAdapterView(AdapterContract.View adapterView) {
this.adapterView = adapterView;
@@ -66,23 +92,43 @@ public void setAdapterView(AdapterContract.View adapterView) {
view.startMovieDetailPage(this.adapterModel.getItem(position).getLinkUrl()));
}
+ /* MainContract.Presenter */
@Override
public void setAdapterModel(AdapterContract.Model adapterModel) {
this.adapterModel = adapterModel;
}
+ @Override
+ public void onOrderSelectionChanged(MovieSortStrategy sortStrategy) {
+ if(sortStrategy != null) {
+ adapterModel.updateItems(sortStrategy.sort(adapterModel.getItemList()));
+ }
+ }
+
@SuppressLint("CheckResult")
private void getMovieList(){
+ if(!isNetworkConnected) {
+ view.makeToast("인터넷 상태를 확인해 주세요 !");
+ return;
+ }
+
+ // RXJava 특성상 Subscribe 하고 있어서
+ // activity_movie 의 progressBar Visibility 가 변경된다.
isLoading.set(true);
+ // repository 에 접근하는 객체는 Singleton 으로 받아옴
+ // searchMovies 의 return value 가 Single 객체이므로 딱 한번만 데이터를 발행
+ // subscribe 에서 발행된 데이터를 Consumer 가 처리하는 구조
repository.searchMovies(MovieMapper.toRequest(searchKey, PAGE_UNIT, (PAGE_UNIT * currentPage++) + 1))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
+ // 받아온 SearchMovieResponse 는 어디서 처리하는거지
- // 로딩 flag OFF
+ // 로딩 flag OFF (Observing)
isLoading.set(false);
+ // 받아온 결과 목록
List movieList = response.getMovieList();
if(movieList.size() == 0){ // 검색 결과가 없는 경우
@@ -98,6 +144,8 @@ private void getMovieList(){
isEndOfPage = true;
}
+ // 선택한 정렬 옵션으로 정렬
+
// 어댑터에 리스트 추가
adapterModel.addItems(movieList);
@@ -111,4 +159,7 @@ private void getMovieList(){
view.onSearchResultEmpty(searchKey);
});
}
+
+
+
}
diff --git a/app/src/main/java/com/example/yeseul/movieapp/view/main/MovieListAdapter.java b/app/src/main/java/com/example/yeseul/movieapp/view/main/MovieListAdapter.java
index 534964c..b50caaf 100644
--- a/app/src/main/java/com/example/yeseul/movieapp/view/main/MovieListAdapter.java
+++ b/app/src/main/java/com/example/yeseul/movieapp/view/main/MovieListAdapter.java
@@ -21,18 +21,25 @@ public MovieListAdapter(Context context) {
@Override
protected void onBindView(ViewHolder holder, int position) {
+ // ViewHolder 에서 사용할 item 을 binding 해준다.
holder.binding.setItem(itemList.get(position));
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int i) {
+ // ViewHolder 바인딩
View view = LayoutInflater.from(context).inflate(R.layout.holder_movie_item, parent, false);
return new ViewHolder(view);
}
- public class ViewHolder extends RecyclerView.ViewHolder{
+ // static 의미 : nested class 로 상위 클래스의 메소드, 변수를 사용하지 않겠다고 명시
+ // non-static 인 경우엔 상위 클래스의 reference 를 가지고 있음 -> Adapter 가 죽어도 ViewHolder 에서 Adapter 를 가지고 있는 경우 memory leak 발생
+ // adapter 가 한 곳에서만 사용된다면 non-static 도 상관없다.
+ public static class ViewHolder extends RecyclerView.ViewHolder{
+ // binding 만 하고 onBindView 할 때 item 을 설정해주면
+ // RXJava 를 이용해서 자동적으로 ViewHolder 의 값이 변경되게 함
HolderMovieItemBinding binding;
public ViewHolder(@NonNull View itemView) {
diff --git a/app/src/main/res/layout/holder_movie_item.xml b/app/src/main/res/layout/holder_movie_item.xml
index 0ad3707..66ea5b3 100644
--- a/app/src/main/res/layout/holder_movie_item.xml
+++ b/app/src/main/res/layout/holder_movie_item.xml
@@ -38,14 +38,15 @@
android:lineSpacingExtra="@dimen/padding_ultra_small"
boldText="@{item.title}"/>
+
+ android:numStars="5"
+ rating="@{item.userRating}"/>
+
+
검색
에 대한 검색결과가 없습니다.
+
+ 사전순
+
+ 정렬 방법
+ 확인
+ 취소
+
+
+
+ - 사전순
+ - 최신순
+ - 오래된순
+ - 높은 평점 순
+ - 낮은 평점 순
+