Bläddra i källkod

[검색][New] API 적용 중 - 계속

hyodong.min 6 år sedan
förälder
incheckning
9be2990d4a

+ 17 - 0
app/src/main/java/kr/co/zumo/app/lifeplus/bean/api/SearchLatestTagResultBean.java

@@ -0,0 +1,17 @@
+/*
+ * COPYRIGHT (c) 2018 All rights reserved by HANWHA LIFE.
+ */
+package kr.co.zumo.app.lifeplus.bean.api;
+
+/**
+ * SearchLatestTagResultBean
+ * <pre>
+ * </pre>
+ *
+ * @author 민효동
+ * @version 1.0
+ * @history 민효동   [2019. 1. 11.]   [최초 작성]
+ * @since 2019. 1. 11.
+ */
+public class SearchLatestTagResultBean extends LifeplusAPIResultListBean<TagBean> {
+}

+ 4 - 0
app/src/main/java/kr/co/zumo/app/lifeplus/bean/api/TagBean.java

@@ -21,6 +21,10 @@ public class TagBean extends TagNumberBean {
   @SerializedName("useCnt")
   private int useCount;
 
+  public TagBean(String tagName) {
+    this.tagName = tagName;
+  }
+
   public String getTagName() {
     return tagName;
   }

+ 32 - 3
app/src/main/java/kr/co/zumo/app/lifeplus/model/LifeplusPreferences.java

@@ -37,6 +37,7 @@ public class LifeplusPreferences {
   public final static String TUTORIAL_LISTICLE_DONE = "tutorial_listicle_done";
   public final static String TUTORIAL_BUCKET_LIST_DETAIL_DONE = "tutorial_bucket_list_detail_done";
   public final static String PROMOTION_SKIP_TIME = "current_time_millis";
+  public final static String TAG_LATEST_LIST = "tag_latest_list";
 
 
   /**
@@ -260,12 +261,17 @@ public class LifeplusPreferences {
   }
 
   public String getDebugTarget(String defaultString) {
-    if(null == defaultString) {
+    if (null == defaultString) {
       defaultString = "";
     }
     return preferences.get(DEBUG_TARGET, defaultString);
   }
 
+  /**
+   * 튜토리얼 - 리스트클 완료 확인
+   *
+   * @param isDone
+   */
   public void setTutorialListicleDone(boolean isDone) {
     preferences.put(TUTORIAL_LISTICLE_DONE, isDone);
   }
@@ -274,6 +280,11 @@ public class LifeplusPreferences {
     return preferences.get(TUTORIAL_LISTICLE_DONE, false);
   }
 
+  /**
+   * 튜토리얼 - 버킷 리스트 완료 왁인
+   *
+   * @param isDone
+   */
   public void setTutorialBucketListDone(boolean isDone) {
     preferences.put(TUTORIAL_BUCKET_LIST_DETAIL_DONE, isDone);
   }
@@ -282,11 +293,29 @@ public class LifeplusPreferences {
     return preferences.get(TUTORIAL_BUCKET_LIST_DETAIL_DONE, false);
   }
 
-  public void setPromotionSkipTime(String currentDate){
+  /**
+   * 프로모션 - 오늘 하루 보지않기 저장
+   *
+   * @param currentDate
+   */
+  public void setPromotionSkipTime(String currentDate) {
     preferences.put(PROMOTION_SKIP_TIME, currentDate);
   }
 
-  public String getPromotionSkipTime(){
+  public String getPromotionSkipTime() {
     return preferences.get(PROMOTION_SKIP_TIME, "");
   }
+
+  /**
+   * 최근 검색 태그 저장
+   *
+   * @param listString
+   */
+  public void setTagLatestList(String listString) {
+    preferences.put(TAG_LATEST_LIST, listString);
+  }
+
+  public String getTagLatestList() {
+    return preferences.get(TAG_LATEST_LIST, "");
+  }
 }

+ 2 - 1
app/src/main/java/kr/co/zumo/app/lifeplus/view/Event.java

@@ -193,7 +193,8 @@ public class Event {
   public static final int LOADED_COUPON = 1038;
   public static final int TAG_POPULAR = 1039;
   public static final int TAG_LATEST = 1040;
-  public static final int TAG_RESULT = 1041;
+  public static final int TAG_AUTO_COMPLETION = 1041;
+  public static final int TAG_SEARCH = 1042;
 
   // boolean
   public static final String TRUE = "TRUE";

+ 7 - 2
app/src/main/java/kr/co/zumo/app/lifeplus/view/screen/search/ISearchView.java

@@ -5,6 +5,7 @@ package kr.co.zumo.app.lifeplus.view.screen.search;
 
 import java.util.List;
 
+import kr.co.zumo.app.lifeplus.bean.api.LifeplusContentsBean;
 import kr.co.zumo.app.lifeplus.bean.api.TagBean;
 import kr.co.zumo.app.lifeplus.view.IView;
 
@@ -28,11 +29,15 @@ public interface ISearchView extends IView {
 
   void drawPopularTag(List<TagBean> tagBeans);
 
-  void drawResultTag(List<TagBean> tagBeans);
+  void drawAutoCompletionTag(List<TagBean> tagBeans);
+
+  void drawResultContents(List<LifeplusContentsBean> tagBeans);
 
   void setVisibleLatestTag(boolean isVisible);
 
   void setVisiblePopularTag(boolean isVisible);
 
-  void setVisibleResultTag(boolean isVisible);
+  void setVisibleAutoCompletionTag(boolean isVisible);
+
+  void setVisibleContents(boolean isVisible);
 }

+ 47 - 16
app/src/main/java/kr/co/zumo/app/lifeplus/view/screen/search/SearchFragment.java

@@ -11,14 +11,18 @@ import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.text.Editable;
 import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
 import android.widget.TextView;
 
 import java.util.List;
 
 import kr.co.zumo.app.R;
+import kr.co.zumo.app.lifeplus.bean.api.LifeplusContentsBean;
 import kr.co.zumo.app.lifeplus.bean.api.TagBean;
 import kr.co.zumo.app.lifeplus.helper.ActionBarHelper;
 import kr.co.zumo.app.lifeplus.helper.NavigationBar;
@@ -44,10 +48,12 @@ public class SearchFragment extends FragmentBase<SearchPresenter> implements ISe
 
   private View containerLatestTag;
   private View containerPopularTag;
-  private View containerResult;
+  private View containerAutoCompletion;
+  private View containerResultContents;
   private RecyclerView recyclerViewLatestTag;
   private RecyclerView recyclerViewPopularTag;
-  private RecyclerView recyclerViewResult;
+  private RecyclerView recyclerViewAutoCompletion;
+  private RecyclerView recyclerViewResultContents;
 
   @Override
   protected View onAfterCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@@ -72,15 +78,18 @@ public class SearchFragment extends FragmentBase<SearchPresenter> implements ISe
     editSearch = findViewById(R.id.edit_search);
     textHash = findViewById(R.id.text_hash);
     containerLatestTag = findViewById(R.id.container_latest_search_tag);
-    containerPopularTag = findViewById(R.id.container_papular_tag);
-    containerResult = findViewById(R.id.container_result);
+    containerPopularTag = findViewById(R.id.container_popular_tag);
+    containerAutoCompletion = findViewById(R.id.container_auto_completion);
+    containerResultContents = findViewById(R.id.container_result_contents);
     recyclerViewLatestTag = findViewById(R.id.recycler_view_latest_tage);
     recyclerViewPopularTag = findViewById(R.id.recycler_view_popular_tag);
-    recyclerViewResult = findViewById(R.id.recycler_view_result);
+    recyclerViewAutoCompletion = findViewById(R.id.recycler_view_auto_completion);
+    recyclerViewResultContents = findViewById(R.id.recycler_view_result_contents);
 
     recyclerViewLatestTag.setLayoutManager(new LinearLayoutManager(getContext()));
     recyclerViewPopularTag.setLayoutManager(new LinearLayoutManager(getContext()));
-    recyclerViewResult.setLayoutManager(new LinearLayoutManager(getContext()));
+    recyclerViewAutoCompletion.setLayoutManager(new LinearLayoutManager(getContext()));
+    recyclerViewResultContents.setLayoutManager(new LinearLayoutManager(getContext()));
 
     editSearch.addTextChangedListener(new TextWatcher() {
       @Override
@@ -96,6 +105,17 @@ public class SearchFragment extends FragmentBase<SearchPresenter> implements ISe
         presenter.afterTextChanged(s);
       }
     });
+
+    editSearch.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+      @Override
+      public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+          presenter.onClickSearch();
+          return true;
+        }
+        return false;
+      }
+    });
   }
 
   @Override
@@ -112,7 +132,7 @@ public class SearchFragment extends FragmentBase<SearchPresenter> implements ISe
   protected void onAfterDestroyView() {
     popularTagListAdapter = null;
     latestTagListAdapter = null;
-    resultTagListAdapter = null;
+    autoCompletionTagListAdapter = null;
   }
 
   @Override
@@ -145,10 +165,11 @@ public class SearchFragment extends FragmentBase<SearchPresenter> implements ISe
 
   TagListAdapter latestTagListAdapter;
   TagListAdapter popularTagListAdapter;
-  TagListAdapter resultTagListAdapter;
+  TagListAdapter autoCompletionTagListAdapter;
 
   @Override
   public void drawLatestTag(List<TagBean> tagBeans) {
+    Log.i("APP# SearchFragment | drawLatestTag", "| tagBeans.size(): " + tagBeans.size());
     if (null == latestTagListAdapter) {
       latestTagListAdapter = new TagListAdapter(tagBeans, event -> {
         presenter.onEvent(event.clone().integer(Event.TAG_LATEST).build());
@@ -174,18 +195,23 @@ public class SearchFragment extends FragmentBase<SearchPresenter> implements ISe
   }
 
   @Override
-  public void drawResultTag(List<TagBean> tagBeans) {
-    if (null == resultTagListAdapter) {
-      resultTagListAdapter = new TagListAdapter(tagBeans, event -> {
-        presenter.onEvent(event.clone().integer(Event.TAG_RESULT).build());
+  public void drawAutoCompletionTag(List<TagBean> tagBeans) {
+    if (null == autoCompletionTagListAdapter) {
+      autoCompletionTagListAdapter = new TagListAdapter(tagBeans, event -> {
+        presenter.onEvent(event.clone().integer(Event.TAG_AUTO_COMPLETION).build());
       });
-      recyclerViewResult.setAdapter(resultTagListAdapter);
+      recyclerViewAutoCompletion.setAdapter(autoCompletionTagListAdapter);
     }
     else {
-      resultTagListAdapter.update(tagBeans);
+      autoCompletionTagListAdapter.update(tagBeans);
     }
   }
 
+  @Override
+  public void drawResultContents(List<LifeplusContentsBean> tagBeans) {
+    Log.i("APP# SearchFragment | drawResultContents", "|" + " result.size(): " + tagBeans.size());
+  }
+
   @Override
   public void setVisibleLatestTag(boolean isVisible) {
     containerLatestTag.setVisibility(isVisible ? View.VISIBLE : View.GONE);
@@ -197,7 +223,12 @@ public class SearchFragment extends FragmentBase<SearchPresenter> implements ISe
   }
 
   @Override
-  public void setVisibleResultTag(boolean isVisible) {
-    containerResult.setVisibility(isVisible ? View.VISIBLE : View.GONE);
+  public void setVisibleAutoCompletionTag(boolean isVisible) {
+    containerAutoCompletion.setVisibility(isVisible ? View.VISIBLE : View.GONE);
+  }
+
+  @Override
+  public void setVisibleContents(boolean isVisible) {
+    containerResultContents.setVisibility(isVisible ? View.VISIBLE : View.GONE);
   }
 }

+ 175 - 1
app/src/main/java/kr/co/zumo/app/lifeplus/view/screen/search/SearchModel.java

@@ -3,15 +3,29 @@
  */
 package kr.co.zumo.app.lifeplus.view.screen.search;
 
+import android.util.Log;
+
+import com.google.gson.Gson;
+
+import java.util.ArrayList;
 import java.util.List;
 
 import io.reactivex.disposables.Disposable;
+import kr.co.zumo.app.lifeplus.bean.api.KeywordRequestBean;
+import kr.co.zumo.app.lifeplus.bean.api.LifeplusContentsBean;
 import kr.co.zumo.app.lifeplus.bean.api.RequestBean;
+import kr.co.zumo.app.lifeplus.bean.api.SearchAutoCompletionResultBean;
+import kr.co.zumo.app.lifeplus.bean.api.SearchLatestTagResultBean;
 import kr.co.zumo.app.lifeplus.bean.api.SearchPopularTagResultBean;
+import kr.co.zumo.app.lifeplus.bean.api.SearchRequestBean;
+import kr.co.zumo.app.lifeplus.bean.api.SearchResultBean;
 import kr.co.zumo.app.lifeplus.bean.api.TagBean;
 import kr.co.zumo.app.lifeplus.model.Model;
+import kr.co.zumo.app.lifeplus.model.SuperModel;
 import kr.co.zumo.app.lifeplus.model.module.APIError;
 import kr.co.zumo.app.lifeplus.model.module.APIModuleSimpleListener;
+import kr.co.zumo.app.lifeplus.model.module.APISearchAutoCompletionModule;
+import kr.co.zumo.app.lifeplus.model.module.APISearchModule;
 import kr.co.zumo.app.lifeplus.model.module.APISearchPopularTageModule;
 import kr.co.zumo.app.lifeplus.view.Event;
 
@@ -26,8 +40,17 @@ import kr.co.zumo.app.lifeplus.view.Event;
  * @since 2019. 1. 7.
  */
 public class SearchModel extends Model {
-  Disposable disposablePopular;
+
+  public static final String HASH = "#";
+
+  protected Disposable disposablePopular;
+  protected Disposable disposableSearch;
+  protected Disposable disposableAutoCompletion;
   protected List<TagBean> popularTagBeans;
+  protected List<TagBean> latestTagBeans;
+  protected List<TagBean> autoCompletionTagBeans;
+  protected List<LifeplusContentsBean> resultContentsBeans;
+  private String tag;
 
   @Override
   protected void createViewInternal() {
@@ -55,6 +78,8 @@ public class SearchModel extends Model {
       disposablePopular.dispose();
       disposablePopular = null;
     }
+    stopSearch();
+    stopAutoCompletion();
   }
 
   @Override
@@ -67,11 +92,53 @@ public class SearchModel extends Model {
 
   }
 
+  public void saveLatestTag(String tag) {
+    Log.i("APP# SearchModel | saveLatestTag", "|" + " save tag => " + tag);
+    latestTagBeans.add(0, new TagBean(toTag(tag)));
+    if (latestTagBeans.size() > 5) {
+      removeLatestTag(5);
+    }
+
+    SearchLatestTagResultBean resultBean = new SearchLatestTagResultBean();
+    resultBean.setData(latestTagBeans);
+    SuperModel.getInstance().getPreferences().setTagLatestList(resultBean.toJson());
+  }
+
+  public void removeLatestTag(int index) {
+    TagBean tagBean = latestTagBeans.remove(index);
+    Log.i("APP# SearchModel | removeLatestTag", "|" + " latest removed: " + latestTagBeans.size() + ", tag: " + tagBean.getTagName());
+  }
+
+  public void loadLatest() {
+    SearchLatestTagResultBean resultBean = new Gson().fromJson(SuperModel.getInstance().getPreferences().getTagLatestList(), SearchLatestTagResultBean.class);
+    if (null != resultBean) {
+      latestTagBeans = resultBean.getData();
+      // # 붙여줌
+      for (TagBean tagBean : latestTagBeans) {
+        tagBean.setTagName(toTag(tagBean.getTagName()));
+      }
+    }
+    else {
+      latestTagBeans = new ArrayList<>();
+    }
+    onResult(new Event.Builder(Event.SUCCESS).integer(Event.TAG_LATEST).build());
+  }
+
+  public List<TagBean> getLatestTagBeans() {
+    return latestTagBeans;
+  }
+
   protected void loadPopular() {
     disposablePopular = new APISearchPopularTageModule().call(new RequestBean(), new APIModuleSimpleListener<SearchPopularTagResultBean>(waiterCaller) {
       @Override
       public void onApiSuccess(SearchPopularTagResultBean resultBean) {
         popularTagBeans = resultBean.getData();
+
+        // # 붙여줌
+        for (TagBean tagBean : popularTagBeans) {
+          tagBean.setTagName(toTag(tagBean.getTagName()));
+        }
+
         onResult(new Event.Builder(Event.SUCCESS).integer(Event.TAG_POPULAR).build());
       }
 
@@ -85,4 +152,111 @@ public class SearchModel extends Model {
   public List<TagBean> getPopularTagBeans() {
     return popularTagBeans;
   }
+
+  // 첫 글자는 hash 로 만들어줌
+  protected String toTag(String s) {
+    if (s.length() > 0 && HASH.equals(String.valueOf(s.charAt(0))) == false) {
+      s = HASH + s;
+    }
+    return s;
+  }
+
+  public void setTag(String s) {
+    tag = toTag(s);
+    Log.w("APP# SearchModel | setTag", "|" + "tag ==> " + tag);
+  }
+
+  public String getTag() {
+    return tag;
+  }
+
+  private void stopSearch() {
+    if (null != disposableSearch) {
+      disposableSearch.dispose();
+      disposableSearch = null;
+    }
+  }
+
+  public void search() {
+    stopSearch();
+    disposableSearch = new APISearchModule().call(getSearchRequestBean(), new APIModuleSimpleListener<SearchResultBean>() {
+      @Override
+      public void onApiSuccess(SearchResultBean resultBean) {
+        resultContentsBeans = resultBean.getData();
+        onResult(new Event.Builder(Event.SUCCESS).integer(Event.TAG_SEARCH).build());
+      }
+
+      @Override
+      public void onApiError(String errorMessage, APIError error) {
+        onResult(new Event.Builder(Event.ERROR).integer(Event.TAG_SEARCH).string(errorMessage).build());
+
+      }
+    });
+  }
+
+  public List<LifeplusContentsBean> getResultContentsBeans() {
+    return resultContentsBeans;
+  }
+
+  private SearchRequestBean getSearchRequestBean() {
+    // 공백 제거
+//    String searchTag = tag.replace(' ', Character.MIN_VALUE);
+    String searchTag = tag.replace(" ", "");
+    SearchRequestBean requestBean = new SearchRequestBean(searchTag);
+    Log.w("APP# SearchModel | search", "|" + " search ---> " + requestBean.toPrettyJson());
+    return requestBean;
+  }
+
+  private void stopAutoCompletion() {
+    if (null != disposableAutoCompletion) {
+      disposableAutoCompletion.dispose();
+      disposableAutoCompletion = null;
+    }
+  }
+
+  public void loadAutoCompletion() {
+    // 마지막 태그만 추출
+    Log.w("APP# SearchModel | loadAutoCompletion", "|" + " auto before: " + tag);
+    String[] tags = tag.replace(HASH, "").split(" ");
+    String autoCompletionTag = "";
+    if (tags.length > 0) {
+      autoCompletionTag = tags[tags.length - 1];
+    }
+    Log.w("APP# SearchModel | loadAutoCompletion", "|" + " auto after: " + autoCompletionTag);
+    if (autoCompletionTag.length() == 0) {
+      Log.w("APP# SearchModel | loadAutoCompletion", "|" + " auto after: return");
+      return;
+    }
+
+    stopAutoCompletion();
+    disposableAutoCompletion = new APISearchAutoCompletionModule().call(new KeywordRequestBean(autoCompletionTag), new APIModuleSimpleListener<SearchAutoCompletionResultBean>() {
+      @Override
+      public void onApiSuccess(SearchAutoCompletionResultBean resultBean) {
+        autoCompletionTagBeans = resultBean.getData();
+
+        // todo 3개 이상 일 때 이전 태그는 ... 처리 => view 단에서 처리 필요
+        StringBuilder stringBuilder = new StringBuilder();
+        int len = tags.length - 1;
+        for (int i = 0; i < len; ++i) {
+          stringBuilder.append(HASH).append(tags[i]).append(" ");
+        }
+        String header = stringBuilder.append(HASH).toString();
+
+        for (TagBean tagBean : autoCompletionTagBeans) {
+          tagBean.setTagName(header + tagBean.getTagName());
+        }
+
+        onResult(new Event.Builder(Event.SUCCESS).integer(Event.TAG_AUTO_COMPLETION).build());
+      }
+
+      @Override
+      public void onApiError(String errorMessage, APIError error) {
+        onResult(new Event.Builder(Event.ERROR).integer(Event.TAG_AUTO_COMPLETION).string(errorMessage).build());
+      }
+    });
+  }
+
+  public List<TagBean> getAutoCompletionBeans() {
+    return autoCompletionTagBeans;
+  }
 }

+ 115 - 11
app/src/main/java/kr/co/zumo/app/lifeplus/view/screen/search/SearchPresenter.java

@@ -7,8 +7,13 @@ import android.text.Editable;
 import android.text.SpannableStringBuilder;
 import android.util.Log;
 
+import java.util.List;
+
+import kr.co.zumo.app.lifeplus.bean.api.LifeplusContentsBean;
+import kr.co.zumo.app.lifeplus.bean.api.TagBean;
 import kr.co.zumo.app.lifeplus.helper.NavigationBar;
 import kr.co.zumo.app.lifeplus.supervisor.ScreenID;
+import kr.co.zumo.app.lifeplus.util.StringUtil;
 import kr.co.zumo.app.lifeplus.view.CharFinder;
 import kr.co.zumo.app.lifeplus.view.DoubleChecker;
 import kr.co.zumo.app.lifeplus.view.Event;
@@ -28,17 +33,55 @@ public class SearchPresenter extends Presenter<SearchModel, ISearchView> {
 
   private CharFinder finder;
   private DoubleChecker doubleCheckerPopular;
+  private DoubleChecker doubleCheckerLatest;
 
   public SearchPresenter(SearchModel model, ISearchView view) {
     super(model, view);
 
     finder = new CharFinder();
     doubleCheckerPopular = new DoubleChecker(this::renderPopular);
+    doubleCheckerLatest = new DoubleChecker(this::renderLatest);
+  }
+
+  private void renderLatest() {
+    List<TagBean> list = model.getLatestTagBeans();
+    if (null != list && list.size() > 0) {
+      view.setVisibleLatestTag(true);
+      view.drawLatestTag(list);
+    }
+    else {
+      view.setVisibleLatestTag(false);
+    }
   }
 
   private void renderPopular() {
-    view.setVisiblePopularTag(true);
-    view.drawPopularTag(model.getPopularTagBeans());
+    List<TagBean> list = model.getPopularTagBeans();
+    if (null != list && list.size() > 0) {
+      view.setVisiblePopularTag(true);
+      view.drawPopularTag(list);
+    }
+  }
+
+  private void renderAutoCompletion() {
+    List<TagBean> list = model.getAutoCompletionBeans();
+    if (null != list && list.size() > 0) {
+      view.setVisibleLatestTag(false);
+      view.setVisiblePopularTag(false);
+      view.setVisibleContents(false);
+      view.setVisibleAutoCompletionTag(true);
+      view.drawAutoCompletionTag(list);
+    }
+  }
+
+  private void renderResultContents() {
+    List<LifeplusContentsBean> list = model.getResultContentsBeans();
+    if (null != list && list.size() > 0) {
+      view.setVisibleLatestTag(false);
+      view.setVisiblePopularTag(false);
+      view.setVisibleAutoCompletionTag(false);
+      view.setVisibleContents(true);
+      view.drawResultContents(list);
+    }
   }
 
   @Override
@@ -64,6 +107,7 @@ public class SearchPresenter extends Presenter<SearchModel, ISearchView> {
   @Override
   protected void startInternalOnce() {
     model.loadPopular();
+    model.loadLatest();
   }
 
   @Override
@@ -89,31 +133,72 @@ public class SearchPresenter extends Presenter<SearchModel, ISearchView> {
 
   @Override
   protected void onEventInternal(Event event) {
-
+    int integer;
+    int index;
+    String tag;
+    switch (event.getEventId()) {
+      case Event.CLICK:
+        integer = event.getInteger();
+        index = event.getIndex();
+        if (integer == Event.TAG_LATEST) {
+          // 최근 검색 태그
+          tag = model.getLatestTagBeans().get(index).getTagName();
+          search(tag);
+        }
+        else if (integer == Event.TAG_POPULAR) {
+          // 인기 태그
+          tag = model.getPopularTagBeans().get(index).getTagName();
+          search(tag);
+        }
+        else if (integer == Event.TAG_AUTO_COMPLETION) {
+          tag = model.getAutoCompletionBeans().get(index).getTagName();
+          search(tag);
+        }
+        break;
+      case Event.DELETE:
+        integer = event.getInteger();
+        index = event.getIndex();
+        if (integer == Event.TAG_LATEST) {
+          // 최근 검색 태그 삭제
+          model.removeLatestTag(index);
+          renderLatest();
+        }
+        break;
+      default:
+        break;
+    }
   }
 
   @Override
   public void onScreenReady() {
     view.setVisibleLatestTag(false);
     view.setVisiblePopularTag(false);
-    view.setVisibleResultTag(false);
+    view.setVisibleAutoCompletionTag(false);
+    view.setVisibleContents(false);
 
     doubleCheckerPopular.checkFirst();
+    doubleCheckerLatest.checkFirst();
   }
 
   @Override
   public void onResult(Event event) {
-
     switch (event.getEventId()) {
       case Event.SUCCESS:
         if (event.getInteger() == Event.TAG_POPULAR) {
           doubleCheckerPopular.checkSecond();
         }
+        else if (event.getInteger() == Event.TAG_LATEST) {
+          doubleCheckerLatest.checkSecond();
+        }
+        else if (event.getInteger() == Event.TAG_AUTO_COMPLETION) {
+          // 자동 완성 결과
+          renderAutoCompletion();
+        }
+        else if (event.getInteger() == Event.TAG_SEARCH) {
+          renderResultContents();
+        }
         break;
       case Event.ERROR:
-        if (event.getInteger() == Event.TAG_POPULAR) {
-
-        }
         showErrorDialog(event.getString());
         break;
       default:
@@ -121,15 +206,29 @@ public class SearchPresenter extends Presenter<SearchModel, ISearchView> {
     }
   }
 
+  private void search(String tag) {
+    if (StringUtil.isFull(tag)) {
+      model.saveLatestTag(tag);
+      model.setTag(tag);
+      model.search();
+    }
+  }
+
   public void onSearchTextChanged(CharSequence s, int start, int before, int count) {
     int len = s.length();
+    Log.i("APP# SearchPresenter | onSearchTextChanged", "| " + s);
 
     /*
     - 텍스트가 입력되면 guide hash 보통 굵기, 빈칸이면 볼드 처리
      */
-
     view.setBoldGuideHash(len == 0);
-    Log.i("APP# SearchPresenter | onSearchTextChanged", "|" + s);
+
+    // 이전 태그와 다를 때 자동 완성 로딩
+    String prevTag = model.getTag();
+    model.setTag(s.toString());
+    if (model.getTag().equals(prevTag) == false) {
+      model.loadAutoCompletion();
+    }
   }
 
 
@@ -175,7 +274,7 @@ public class SearchPresenter extends Presenter<SearchModel, ISearchView> {
     }
   }
 
-  String hash = "#";
+  String hash = SearchModel.HASH;
 
   private Editable replaceHash(Editable s) {
     Editable ab = removeHash(s);
@@ -208,6 +307,11 @@ public class SearchPresenter extends Presenter<SearchModel, ISearchView> {
   @Override
   public void onNavigationClickSearch(NavigationBar navigationBar) {
     // search
+    onClickSearch();
+  }
 
+  public void onClickSearch() {
+    String tag = model.getTag();
+    search(tag);
   }
 }

+ 4 - 0
app/src/main/java/kr/co/zumo/app/lifeplus/view/screen/search/TagListHolder.java

@@ -40,6 +40,10 @@ public class TagListHolder extends RecyclerView.ViewHolder {
       textTag.setOnClickListener(v -> {
         listener.onEvent(new Event.Builder(Event.CLICK).index(getAdapterPosition()).build());
       });
+
+      imageDelete.setOnClickListener(v -> {
+        listener.onEvent(new Event.Builder(Event.DELETE).index(getAdapterPosition()).build());
+      });
     }
   }
 }

+ 80 - 48
app/src/main/res/layout/fragment_search.xml

@@ -42,7 +42,7 @@
       android:layout_marginEnd="47dp"
       android:background="@null"
       android:hint="@string/search_hint"
-      android:imeOptions="actionDone"
+      android:imeOptions="actionSearch"
       android:inputType="text"
       android:maxLines="1"
       android:paddingTop="10dp"
@@ -60,68 +60,100 @@
 
   </android.support.constraint.ConstraintLayout>
 
-  <android.support.constraint.ConstraintLayout
-    android:id="@+id/container_latest_search_tag"
+  <android.support.v4.widget.NestedScrollView
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    app:layout_constraintBottom_toTopOf="@id/container_papular_tag"
+    android:layout_height="0dp"
+    android:fillViewport="true"
+    app:layout_constraintBottom_toBottomOf="parent"
     app:layout_constraintTop_toBottomOf="@+id/container_top"
-    >
+    app:layout_constraintVertical_bias="0">
 
-    <TextView
-      android:id="@+id/text_latest_tag"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:layout_marginStart="25dp"
-      android:layout_marginTop="30dp"
-      android:text="@string/search_latest_search_tag"
-      android:textColor="@color/C000000"
-      android:textSize="15sp"
-      android:textStyle="bold"
-      app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintTop_toTopOf="parent"/>
-
-    <android.support.v7.widget.RecyclerView
-      android:id="@+id/recycler_view_latest_tage"
+    <android.support.constraint.ConstraintLayout
       android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:paddingTop="4dp"
-      app:layout_constraintTop_toBottomOf="@+id/text_latest_tag"/>
+      android:layout_height="wrap_content">
 
-  </android.support.constraint.ConstraintLayout>
+      <android.support.constraint.ConstraintLayout
+        android:id="@+id/container_latest_search_tag"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toTopOf="@id/container_popular_tag"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="0"
+        app:layout_constraintVertical_chainStyle="packed"
+        >
+
+        <TextView
+          android:id="@+id/text_latest_tag"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:layout_marginStart="25dp"
+          android:layout_marginTop="30dp"
+          android:text="@string/search_latest_search_tag"
+          android:textColor="@color/C000000"
+          android:textSize="15sp"
+          android:textStyle="bold"
+          app:layout_constraintStart_toStartOf="parent"
+          app:layout_constraintTop_toTopOf="parent"/>
+
+        <android.support.v7.widget.RecyclerView
+          android:id="@+id/recycler_view_latest_tage"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:nestedScrollingEnabled="false"
+          android:paddingTop="4dp"
+          app:layout_constraintTop_toBottomOf="@+id/text_latest_tag"/>
+
+      </android.support.constraint.ConstraintLayout>
+
+      <android.support.constraint.ConstraintLayout
+        android:id="@+id/container_popular_tag"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/container_latest_search_tag"
+        >
+
+        <TextView
+          android:id="@+id/text_popular_tag"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:layout_marginStart="25dp"
+          android:layout_marginTop="30dp"
+          android:text="@string/search_popular_tag"
+          android:textColor="@color/C000000"
+          android:textSize="15sp"
+          android:textStyle="bold"
+          app:layout_constraintStart_toStartOf="parent"
+          app:layout_constraintTop_toTopOf="parent"/>
+
+        <android.support.v7.widget.RecyclerView
+          android:id="@+id/recycler_view_popular_tag"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:nestedScrollingEnabled="false"
+          android:paddingTop="4dp"
+          app:layout_constraintBottom_toBottomOf="parent"
+          app:layout_constraintTop_toBottomOf="@+id/text_popular_tag"/>
+      </android.support.constraint.ConstraintLayout>
+    </android.support.constraint.ConstraintLayout>
+  </android.support.v4.widget.NestedScrollView>
 
   <android.support.constraint.ConstraintLayout
-    android:id="@+id/container_papular_tag"
+    android:id="@+id/container_auto_completion"
     android:layout_width="match_parent"
     android:layout_height="0dp"
     app:layout_constraintBottom_toBottomOf="parent"
-    app:layout_constraintTop_toBottomOf="@id/container_latest_search_tag"
-    >
-
-    <TextView
-      android:id="@+id/text_popular_tag"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:layout_marginStart="25dp"
-      android:layout_marginTop="30dp"
-      android:text="@string/search_popular_tag"
-      android:textColor="@color/C000000"
-      android:textSize="15sp"
-      android:textStyle="bold"
-      app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintTop_toTopOf="parent"/>
+    app:layout_constraintTop_toBottomOf="@id/container_top"
+    tools:visibility="gone">
 
     <android.support.v7.widget.RecyclerView
-      android:id="@+id/recycler_view_popular_tag"
+      android:id="@+id/recycler_view_auto_completion"
       android:layout_width="match_parent"
-      android:layout_height="0dp"
-      android:paddingTop="4dp"
-      app:layout_constraintBottom_toBottomOf="parent"
-      app:layout_constraintTop_toBottomOf="@+id/text_popular_tag"/>
+      android:layout_height="match_parent"/>
   </android.support.constraint.ConstraintLayout>
 
   <android.support.constraint.ConstraintLayout
-    android:id="@+id/container_result"
+    android:id="@+id/container_result_contents"
     android:layout_width="match_parent"
     android:layout_height="0dp"
     app:layout_constraintBottom_toBottomOf="parent"
@@ -129,7 +161,7 @@
     tools:visibility="gone">
 
     <android.support.v7.widget.RecyclerView
-      android:id="@+id/recycler_view_result"
+      android:id="@+id/recycler_view_result_contents"
       android:layout_width="match_parent"
       android:layout_height="match_parent"/>
   </android.support.constraint.ConstraintLayout>