├── .gitignore
├── README.md
├── app
├── .gitignore
├── app.iml
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── mobitant
│ │ └── bestfood
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── mobitant
│ │ │ └── bestfood
│ │ │ ├── BestFoodInfoActivity.java
│ │ │ ├── BestFoodKeepFragment.java
│ │ │ ├── BestFoodListFragment.java
│ │ │ ├── BestFoodMapFragment.java
│ │ │ ├── BestFoodRegisterActivity.java
│ │ │ ├── BestFoodRegisterImageFragment.java
│ │ │ ├── BestFoodRegisterInputFragment.java
│ │ │ ├── BestFoodRegisterLocationFragment.java
│ │ │ ├── Constant.java
│ │ │ ├── IndexActivity.java
│ │ │ ├── MainActivity.java
│ │ │ ├── MyApp.java
│ │ │ ├── PermissionActivity.java
│ │ │ ├── ProfileActivity.java
│ │ │ ├── ProfileIconActivity.java
│ │ │ ├── adapter
│ │ │ ├── InfoListAdapter.java
│ │ │ ├── KeepListAdapter.java
│ │ │ └── MapListAdapter.java
│ │ │ ├── custom
│ │ │ ├── EndlessRecyclerViewScrollListener.java
│ │ │ └── WorkaroundMapFragment.java
│ │ │ ├── item
│ │ │ ├── FoodInfoItem.java
│ │ │ ├── GeoItem.java
│ │ │ ├── ImageItem.java
│ │ │ ├── KeepItem.java
│ │ │ └── MemberInfoItem.java
│ │ │ ├── lib
│ │ │ ├── BitmapLib.java
│ │ │ ├── DialogLib.java
│ │ │ ├── EtcLib.java
│ │ │ ├── FileLib.java
│ │ │ ├── GeoLib.java
│ │ │ ├── GoLib.java
│ │ │ ├── KeepLib.java
│ │ │ ├── MyLog.java
│ │ │ ├── MyToast.java
│ │ │ ├── RemoteLib.java
│ │ │ └── StringLib.java
│ │ │ └── remote
│ │ │ ├── RemoteService.java
│ │ │ └── ServiceGenerator.java
│ └── res
│ │ ├── drawable
│ │ ├── bg_basic_black_transparent.xml
│ │ ├── bg_basic_gray.xml
│ │ ├── bg_bestfood_drawer.png
│ │ ├── bg_index.png
│ │ ├── bg_round.xml
│ │ ├── bg_round_gray.xml
│ │ ├── button_circle.xml
│ │ ├── button_round.xml
│ │ ├── button_round_green.xml
│ │ ├── button_round_red.xml
│ │ ├── ic_camera.png
│ │ ├── ic_keep_off.png
│ │ ├── ic_keep_on.png
│ │ ├── ic_list.png
│ │ ├── ic_list2.png
│ │ ├── ic_map.png
│ │ ├── ic_person.png
│ │ ├── ic_profile.png
│ │ ├── ic_register.png
│ │ └── ic_tell.png
│ │ ├── layout
│ │ ├── activity_bestfood_info.xml
│ │ ├── activity_bestfood_register.xml
│ │ ├── activity_index.xml
│ │ ├── activity_main.xml
│ │ ├── activity_permission.xml
│ │ ├── activity_profile.xml
│ │ ├── activity_profile_icon.xml
│ │ ├── app_bar_main.xml
│ │ ├── content_main.xml
│ │ ├── fragment_bestfood_keep.xml
│ │ ├── fragment_bestfood_list.xml
│ │ ├── fragment_bestfood_map.xml
│ │ ├── fragment_bestfood_register_image.xml
│ │ ├── fragment_bestfood_register_input.xml
│ │ ├── fragment_bestfood_register_location.xml
│ │ ├── loading_layout.xml
│ │ ├── nav_header_main.xml
│ │ ├── row_bestfood_keep.xml
│ │ ├── row_bestfood_list.xml
│ │ ├── row_bestfood_map.xml
│ │ └── toolbar.xml
│ │ ├── menu
│ │ ├── activity_main_drawer.xml
│ │ ├── menu_close.xml
│ │ └── menu_submit.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-v21
│ │ └── styles.xml
│ │ └── values
│ │ ├── arrays.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── mobitant
│ └── bestfood
│ └── ExampleUnitTest.java
└── web
├── .gitignore
├── app.js
├── bin
└── www
├── db.js
├── package.json
├── public
├── css
│ └── style.css
└── img
│ ├── 296.png
│ ├── 439.png
│ ├── 444.png
│ └── 7_1457590813472.png
├── routes
├── food.js
├── keep.js
└── member.js
└── views
├── error.ejs
└── index.ejs
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.bak
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # and_node
2 | 오픈소스를 활용한 안드로이드 서비스 개발(with Node.js)
3 |
4 | ## MariaDB 테이블 생성문
5 |
6 |
7 | **bestfood_info 테이블 생성문**
8 | CREATE TABLE IF NOT EXISTS `bestfood_info` (
9 | `seq` int(11) NOT NULL AUTO_INCREMENT,
10 | `member_seq` int(11) NOT NULL,
11 | `name` varchar(20) NOT NULL,
12 | `tel` varchar(20) NOT NULL,
13 | `address` varchar(50) NOT NULL,
14 | `latitude` double NOT NULL,
15 | `longitude` double NOT NULL,
16 | `description` varchar(500) NOT NULL,
17 | `keep_cnt` int(11) NOT NULL DEFAULT '0',
18 | `reg_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
19 | `PRIMARY KEY (`seq`)
20 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
21 |
22 |
23 | **bestfood_info_image 테이블 생성문**
24 | CREATE TABLE IF NOT EXISTS `bestfood_info_image` (
25 | `seq` int(11) NOT NULL AUTO_INCREMENT,
26 | `info_seq` int(11) NOT NULL,
27 | `filename` varchar(30) NOT NULL,
28 | `image_memo` varchar(100) NOT NULL,
29 | `reg_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
30 | PRIMARY KEY (`seq`)
31 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
32 |
33 |
34 | **bestfood_keep 테이블 생성문**
35 | CREATE TABLE IF NOT EXISTS `bestfood_keep` (
36 | `seq` int(11) NOT NULL AUTO_INCREMENT,
37 | `member_seq` int(11) NOT NULL,
38 | `info_seq` int(11) NOT NULL,
39 | `reg_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
40 | PRIMARY KEY (`seq`)
41 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
42 |
43 |
44 | **bestfood_member 테이블 생성문**
45 | CREATE TABLE IF NOT EXISTS `bestfood_member` (
46 | `seq` int(11) NOT NULL AUTO_INCREMENT,
47 | `phone` varchar(30) NOT NULL,
48 | `name` varchar(30) DEFAULT NULL,
49 | `sextype` varchar(10) DEFAULT NULL,
50 | `birthday` varchar(30) DEFAULT NULL,
51 | `member_icon_filename` varchar(50) DEFAULT NULL,
52 | `reg_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
53 | PRIMARY KEY (`seq`)
54 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
55 |
56 |
57 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion '25.0.3'
6 |
7 | defaultConfig {
8 | applicationId "com.mobitant.bestfood"
9 | minSdkVersion 23
10 | targetSdkVersion 25
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | testCompile 'junit:junit:4.12'
28 |
29 | compile 'com.android.support:appcompat-v7:25.3.1'
30 | compile 'com.android.support:design:25.3.1'
31 | compile 'com.android.support:support-v4:25.3.1'
32 | compile 'com.android.support:recyclerview-v7:25.3.1'
33 | compile 'com.android.support:cardview-v7:25.3.1'
34 |
35 | compile 'com.squareup.retrofit2:retrofit:2.1.0'
36 | compile 'com.squareup.retrofit2:converter-gson:2.1.0'
37 | compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
38 | compile 'com.squareup.okhttp3:okhttp:3.3.1'
39 | compile 'org.parceler:parceler:1.0.0'
40 | compile 'com.google.code.gson:gson:2.7'
41 | compile 'de.hdodenhof:circleimageview:2.1.0'
42 | compile 'com.squareup.picasso:picasso:2.5.2'
43 |
44 | compile 'com.google.android.gms:play-services-base:10.2.6'
45 | compile 'com.google.android.gms:play-services-maps:10.2.6'
46 | }
47 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\Dropbox\MOBITANT_PRJ\Android\sdk1/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/mobitant/bestfood/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
20 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/BestFoodKeepFragment.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.support.annotation.Nullable;
6 | import android.support.v4.app.Fragment;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.support.v7.widget.StaggeredGridLayoutManager;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.TextView;
13 |
14 | import com.google.android.gms.maps.model.LatLng;
15 | import com.mobitant.bestfood.adapter.KeepListAdapter;
16 | import com.mobitant.bestfood.item.FoodInfoItem;
17 | import com.mobitant.bestfood.item.GeoItem;
18 | import com.mobitant.bestfood.item.KeepItem;
19 | import com.mobitant.bestfood.lib.MyLog;
20 | import com.mobitant.bestfood.remote.RemoteService;
21 | import com.mobitant.bestfood.remote.ServiceGenerator;
22 |
23 | import java.util.ArrayList;
24 |
25 | import retrofit2.Call;
26 | import retrofit2.Callback;
27 | import retrofit2.Response;
28 |
29 | /**
30 | * 맛집 즐겨찾기 리스트를 보여주는 프래그먼트
31 | */
32 | public class BestFoodKeepFragment extends Fragment {
33 | private final String TAG = this.getClass().getSimpleName();
34 |
35 | Context context;
36 | int memberSeq;
37 |
38 | RecyclerView keepRecyclerView;
39 | TextView noDataText;
40 |
41 | KeepListAdapter keepListAdapter;
42 |
43 | ArrayList keepList = new ArrayList<>();
44 |
45 | /**
46 | * BestFoodKeepFragment 인스턴스를 생성한다.
47 | * @return BestFoodListFragment 인스턴스
48 | */
49 | public static BestFoodKeepFragment newInstance() {
50 | BestFoodKeepFragment f = new BestFoodKeepFragment();
51 | return f;
52 | }
53 |
54 | /**
55 | * fragment_bestfood_keep.xml 기반으로 뷰를 생성한다.
56 | * @param inflater XML를 객체로 변환하는 LayoutInflater 객체
57 | * @param container null이 아니라면 부모 뷰
58 | * @param savedInstanceState null이 아니라면 이전에 저장된 상태를 가진 객체
59 | * @return 생성한 뷰 객체
60 | */
61 | @Override
62 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
63 | context = this.getActivity();
64 |
65 | memberSeq = ((MyApp)getActivity().getApplication()).getMemberSeq();
66 |
67 | View layout = inflater.inflate(R.layout.fragment_bestfood_keep, container, false);
68 |
69 | return layout;
70 | }
71 |
72 | /**
73 | * 프래그먼트가 일시 중지 상태가 되었다가 다시 보여질 때 호출된다.
74 | * BestFoodInfoActivity가 실행된 후,
75 | * 즐겨찾기 상태가 변경되었을 경우 이를 반영하는 용도로 사용한다.
76 | */
77 | @Override
78 | public void onResume() {
79 | super.onResume();
80 |
81 | MyApp myApp = ((MyApp) getActivity().getApplication());
82 | FoodInfoItem currentInfoItem = myApp.getFoodInfoItem();
83 |
84 | if (keepListAdapter != null && currentInfoItem != null) {
85 | keepListAdapter.setItem(currentInfoItem);
86 | myApp.setFoodInfoItem(null);
87 |
88 | if (keepListAdapter.getItemCount() == 0) {
89 | noDataText.setVisibility(View.VISIBLE);
90 | }
91 | }
92 | }
93 |
94 | /**
95 | * onCreateView() 메소드 뒤에 호출되며 화면 뷰들을 설정한다.
96 | * @param view onCreateView() 메소드에 의해 반환된 뷰
97 | * @param savedInstanceState null이 아니라면 이전에 저장된 상태를 가진 객체
98 | */
99 | @Override
100 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
101 | super.onViewCreated(view, savedInstanceState);
102 | ((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.nav_keep);
103 |
104 | keepRecyclerView = (RecyclerView) view.findViewById(R.id.keep_list);
105 | noDataText = (TextView) view.findViewById(R.id.no_keep);
106 |
107 | keepListAdapter = new KeepListAdapter(context,
108 | R.layout.row_bestfood_keep, keepList, memberSeq);
109 | StaggeredGridLayoutManager layoutManager
110 | = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
111 | layoutManager.setGapStrategy(
112 | StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
113 | keepRecyclerView.setLayoutManager(layoutManager);
114 | keepRecyclerView.setAdapter(keepListAdapter);
115 |
116 | listKeep(memberSeq, GeoItem.getKnownLocation());
117 | }
118 |
119 | /**
120 | * 서버에서 즐겨찾기한 맛집 정보를 조회한다.
121 | * @param memberSeq 사용자 시퀀스
122 | * @param userLatLng 사용자 위도 경도 객체
123 | */
124 | private void listKeep(int memberSeq, LatLng userLatLng) {
125 | RemoteService remoteService = ServiceGenerator.createService(RemoteService.class);
126 |
127 | Call> call
128 | = remoteService.listKeep(memberSeq, userLatLng.latitude, userLatLng.longitude);
129 | call.enqueue(new Callback>() {
130 | @Override
131 | public void onResponse(Call> call,
132 | Response> response) {
133 | ArrayList list = response.body();
134 |
135 | if (list == null) {
136 | list = new ArrayList<>();
137 | }
138 |
139 | noDataText.setVisibility(View.GONE);
140 |
141 | if (response.isSuccessful()) {
142 | MyLog.d(TAG, "list size " + list.size());
143 | if (list.size() == 0) {
144 | noDataText.setVisibility(View.VISIBLE);
145 | } else {
146 | keepListAdapter.setItemList(list);
147 | }
148 | } else {
149 | MyLog.d(TAG, "not success");
150 | }
151 | }
152 |
153 | @Override
154 | public void onFailure(Call> call, Throwable t) {
155 | MyLog.d(TAG, "no internet connectivity");
156 | MyLog.d(TAG, t.toString());
157 | }
158 | });
159 | }
160 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/BestFoodRegisterActivity.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.v4.app.Fragment;
7 | import android.support.v7.app.ActionBar;
8 | import android.support.v7.app.AppCompatActivity;
9 | import android.support.v7.widget.Toolbar;
10 | import android.view.Menu;
11 | import android.view.MenuItem;
12 |
13 | import com.mobitant.bestfood.item.FoodInfoItem;
14 | import com.mobitant.bestfood.item.GeoItem;
15 | import com.mobitant.bestfood.lib.GoLib;
16 | import com.mobitant.bestfood.lib.MyLog;
17 |
18 | /**
19 | * 맛집 등록 액티비티이다.
20 | * 액티비티의 기본적인 화면 구성을 하며 실제 사용자 화면은 프래그먼트로 구성한다.
21 | */
22 | public class BestFoodRegisterActivity extends AppCompatActivity {
23 | private final String TAG = this.getClass().getSimpleName();
24 | public static FoodInfoItem currentItem = null;
25 |
26 | Context context;
27 |
28 | /**
29 | * BestFoodRegisterLocationFragment를 실행하기 위한 기본적인 정보를 설정하고
30 | * 프래그먼트를 실행한다.
31 | */
32 | @Override
33 | protected void onCreate(Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 | setContentView(R.layout.activity_bestfood_register);
36 |
37 | context = this;
38 |
39 | int memberSeq = ((MyApp)getApplication()).getMemberSeq();
40 |
41 | //BestFoodRegisterLocationFragment로 넘길 기본적인 정보를 저장한다.
42 | FoodInfoItem infoItem = new FoodInfoItem();
43 | infoItem.memberSeq = memberSeq;
44 | infoItem.latitude = GeoItem.getKnownLocation().latitude;
45 | infoItem.longitude = GeoItem.getKnownLocation().longitude;
46 |
47 | MyLog.d(TAG, "infoItem " + infoItem.toString());
48 |
49 | setToolbar();
50 |
51 | //BestFoodRegisterLocationFragment를 화면에 보여준다.
52 | GoLib.getInstance().goFragment(getSupportFragmentManager(),
53 | R.id.content_main, BestFoodRegisterLocationFragment.newInstance(infoItem));
54 | }
55 |
56 | /**
57 | * 툴바를 설정한다.
58 | */
59 | private void setToolbar() {
60 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
61 | setSupportActionBar(toolbar);
62 |
63 | ActionBar actionBar = getSupportActionBar();
64 |
65 | if (actionBar != null) {
66 | actionBar.setDisplayHomeAsUpEnabled(true);
67 | actionBar.setTitle(R.string.bestfood_register);
68 | }
69 | }
70 |
71 | /**
72 | * 오른쪽 상단 메뉴를 구성한다.
73 | * 닫기 메뉴만이 설정되어 있는 menu_close.xml를 지정한다.
74 | * @param menu 메뉴 객체
75 | * @return 메뉴를 보여준다면 true, 보여주지 않는다면 false
76 | */
77 | @Override
78 | public boolean onCreateOptionsMenu(Menu menu) {
79 | getMenuInflater().inflate(R.menu.menu_close, menu);
80 | return true;
81 | }
82 |
83 | /**
84 | * 왼쪽 화살표 메뉴(android.R.id.home)를 클릭했을 때와
85 | * 오른쪽 상단 닫기 메뉴를 클릭했을 때의 동작을 지정한다.
86 | * 여기서는 모든 버튼이 액티비티를 종료한다.
87 | * @param item 메뉴 아이템 객체
88 | * @return 메뉴를 처리했다면 true, 그렇지 않다면 false
89 | */
90 | @Override
91 | public boolean onOptionsItemSelected(MenuItem item) {
92 | switch (item.getItemId()) {
93 | case android.R.id.home:
94 | finish();
95 | break;
96 |
97 | case R.id.action_close:
98 | finish();
99 | break;
100 | }
101 |
102 | return true;
103 | }
104 |
105 | /**
106 | * 다른 액티비티를 실행한 결과를 처리하는 메소드
107 | * (실제로는 프래그먼트로 onActivityResult 호출을 전달하기 위한 목적으로 작성)
108 | * @param requestCode 액티비티를 실행하면서 전달한 요청 코드
109 | * @param resultCode 실행한 액티비티가 설정한 결과 코드
110 | * @param data 결과 데이터
111 | */
112 | @Override
113 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
114 | for (Fragment fragment : getSupportFragmentManager().getFragments()) {
115 | fragment.onActivityResult(requestCode, resultCode, data);
116 | }
117 | }
118 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/BestFoodRegisterInputFragment.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood;
2 |
3 | import android.content.Context;
4 | import android.location.Address;
5 | import android.os.Bundle;
6 | import android.support.v4.app.Fragment;
7 | import android.text.Editable;
8 | import android.text.TextWatcher;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.Button;
13 | import android.widget.EditText;
14 | import android.widget.TextView;
15 |
16 | import com.google.android.gms.maps.model.LatLng;
17 | import com.mobitant.bestfood.item.FoodInfoItem;
18 | import com.mobitant.bestfood.lib.EtcLib;
19 | import com.mobitant.bestfood.lib.GeoLib;
20 | import com.mobitant.bestfood.lib.GoLib;
21 | import com.mobitant.bestfood.lib.MyLog;
22 | import com.mobitant.bestfood.lib.MyToast;
23 | import com.mobitant.bestfood.lib.StringLib;
24 | import com.mobitant.bestfood.remote.RemoteService;
25 | import com.mobitant.bestfood.remote.ServiceGenerator;
26 |
27 | import org.parceler.Parcels;
28 |
29 | import okhttp3.ResponseBody;
30 | import retrofit2.Call;
31 | import retrofit2.Callback;
32 | import retrofit2.Response;
33 |
34 | /**
35 | * 맛집 정보를 입력하는 액티비티
36 | */
37 | public class BestFoodRegisterInputFragment extends Fragment implements View.OnClickListener {
38 | public static final String INFO_ITEM = "INFO_ITEM";
39 | private final String TAG = this.getClass().getSimpleName();
40 |
41 | Context context;
42 | FoodInfoItem infoItem;
43 | Address address;
44 |
45 | EditText nameEdit;
46 | EditText telEdit;
47 | EditText descriptionEdit;
48 | TextView currentLength;
49 |
50 | /**
51 | * FoodInfoItem 객체를 인자로 저장하는
52 | * BestFoodRegisterInputFragment 인스턴스를 생성해서 반환한다.
53 | * @param infoItem 맛집 정보를 저장하는 객체
54 | * @return BestFoodRegisterInputFragment 인스턴스
55 | */
56 | public static BestFoodRegisterInputFragment newInstance(FoodInfoItem infoItem) {
57 | Bundle bundle = new Bundle();
58 | bundle.putParcelable(INFO_ITEM, Parcels.wrap(infoItem));
59 |
60 | BestFoodRegisterInputFragment fragment = new BestFoodRegisterInputFragment();
61 | fragment.setArguments(bundle);
62 |
63 | return fragment;
64 | }
65 |
66 | /**
67 | * 프래그먼트가 생성될 때 호출되며 인자에 저장된 FoodInfoItem를
68 | * BestFoodRegisterActivity에 currentItem를 저장한다.
69 | * @param savedInstanceState 프래그먼트가 새로 생성되었을 경우, 이전 상태 값을 가지는 객체
70 | */
71 | @Override
72 | public void onCreate(Bundle savedInstanceState) {
73 | super.onCreate(savedInstanceState);
74 |
75 | if (getArguments() != null) {
76 | infoItem = Parcels.unwrap(getArguments().getParcelable(INFO_ITEM));
77 | if (infoItem.seq != 0) {
78 | BestFoodRegisterActivity.currentItem = infoItem;
79 | }
80 | MyLog.d(TAG, "infoItem " + infoItem);
81 | }
82 | }
83 |
84 | /**
85 | * fragment_bestfood_register_input.xml 기반으로 뷰를 생성한다.
86 | * @param inflater XML를 객체로 변환하는 LayoutInflater 객체
87 | * @param container null이 아니라면 부모 뷰
88 | * @param savedInstanceState null이 아니라면 이전에 저장된 상태를 가진 객체
89 | * @return 생성한 뷰 객체
90 | */
91 | @Override
92 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
93 | context = this.getActivity();
94 | address = GeoLib.getInstance().getAddressString(context,
95 | new LatLng(infoItem.latitude, infoItem.longitude));
96 | MyLog.d(TAG, "address" + address);
97 |
98 | return inflater.inflate(R.layout.fragment_bestfood_register_input, container, false);
99 | }
100 |
101 | /**
102 | * onCreateView() 메소드 뒤에 호출되며 맛집 정보를 입력할 뷰들을 생성한다.
103 | * @param view onCreateView() 메소드에 의해 반환된 뷰
104 | * @param savedInstanceState null이 아니라면 이전에 저장된 상태를 가진 객체
105 | */
106 | @Override
107 | public void onViewCreated(View view, Bundle savedInstanceState) {
108 | super.onViewCreated(view, savedInstanceState);
109 |
110 | currentLength = (TextView) view.findViewById(R.id.current_length);
111 | nameEdit = (EditText) view.findViewById(R.id.bestfood_name);
112 | telEdit = (EditText) view.findViewById(R.id.bestfood_tel);
113 | descriptionEdit = (EditText) view.findViewById(R.id.bestfood_description);
114 | descriptionEdit.addTextChangedListener(new TextWatcher() {
115 | @Override
116 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
117 | }
118 |
119 | @Override
120 | public void onTextChanged(CharSequence s, int start, int before, int count) {
121 | currentLength.setText(String.valueOf(s.length()));
122 | }
123 |
124 | @Override
125 | public void afterTextChanged(Editable s) {
126 | }
127 | });
128 |
129 | EditText addressEdit = (EditText) view.findViewById(R.id.bestfood_address);
130 |
131 | infoItem.address = GeoLib.getInstance().getAddressString(address);
132 | if (!StringLib.getInstance().isBlank(infoItem.address)) {
133 | addressEdit.setText(infoItem.address);
134 | }
135 |
136 | Button prevButton = (Button) view.findViewById(R.id.prev);
137 | prevButton.setOnClickListener(this);
138 |
139 | Button nextButton = (Button) view.findViewById(R.id.next);
140 | nextButton.setOnClickListener(this);
141 | }
142 |
143 | /**
144 | * 클릭이벤트를 처리한다.
145 | * @param v 클릭한 뷰에 대한 정보
146 | */
147 | @Override
148 | public void onClick(View v) {
149 | infoItem.name = nameEdit.getText().toString();
150 | infoItem.tel = telEdit.getText().toString();
151 | infoItem.description = descriptionEdit.getText().toString();
152 | MyLog.d(TAG, "onClick imageItem " + infoItem);
153 |
154 | if (v.getId() == R.id.prev) {
155 | GoLib.getInstance().goFragment(getFragmentManager(),
156 | R.id.content_main, BestFoodRegisterLocationFragment.newInstance(infoItem));
157 | } else if (v.getId() == R.id.next) {
158 | save();
159 | }
160 | }
161 |
162 | /**
163 | * 사용자가 입력한 정보를 확인하고 저장한다.
164 | */
165 | private void save() {
166 | if (StringLib.getInstance().isBlank(infoItem.name)) {
167 | MyToast.s(context, context.getResources().getString(R.string.input_bestfood_name));
168 | return;
169 | }
170 |
171 | if (StringLib.getInstance().isBlank(infoItem.tel)
172 | || !EtcLib.getInstance().isValidPhoneNumber(infoItem.tel)) {
173 | MyToast.s(context, context.getResources().getString(R.string.not_valid_tel_number));
174 | return;
175 | }
176 |
177 | insertFoodInfo();
178 | }
179 |
180 | /**
181 | * 사용자가 입력한 정보를 서버에 저장한다.
182 | */
183 | private void insertFoodInfo() {
184 | MyLog.d(TAG, infoItem.toString());
185 |
186 | RemoteService remoteService = ServiceGenerator.createService(RemoteService.class);
187 |
188 | Call call = remoteService.insertFoodInfo(infoItem);
189 | call.enqueue(new Callback() {
190 | @Override
191 | public void onResponse(Call call, Response response) {
192 | if (response.isSuccessful()) {
193 | int seq = 0;
194 | String seqString = response.body();
195 |
196 | try {
197 | seq = Integer.parseInt(seqString);
198 | } catch (Exception e) {
199 | seq = 0;
200 | }
201 |
202 | if (seq == 0) {
203 | //등록 실패
204 | } else {
205 | infoItem.seq = seq;
206 | goNextPage();
207 | }
208 | } else { // 등록 실패
209 | int statusCode = response.code();
210 | ResponseBody errorBody = response.errorBody();
211 | MyLog.d(TAG, "fail " + statusCode + errorBody.toString());
212 | }
213 | }
214 |
215 | @Override
216 | public void onFailure(Call call, Throwable t) {
217 | MyLog.d(TAG, "no internet connectivity");
218 | }
219 | });
220 | }
221 |
222 | /**
223 | * 맛집 이미지를 등록할 수 있는 프래그먼트로 이동한다.
224 | */
225 | private void goNextPage() {
226 | GoLib.getInstance().goFragmentBack(getFragmentManager(),
227 | R.id.content_main, BestFoodRegisterImageFragment.newInstance(infoItem.seq));
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/Constant.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood;
2 |
3 | public interface Constant {
4 | int MAX_LENGTH_DESCRIPTION = 50;
5 |
6 | int MAP_MAX_ZOOM_LEVEL = 10;
7 | int MAP_ZOOM_LEVEL_DETAIL = 13;
8 |
9 | String ORDER_TYPE_METER = "";
10 | String ORDER_TYPE_FAVORITE = "keep_cnt";
11 | String ORDER_TYPE_RECENT = "reg_date";
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/IndexActivity.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.os.Handler;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.view.View;
9 | import android.widget.Button;
10 | import android.widget.TextView;
11 |
12 | import com.mobitant.bestfood.item.MemberInfoItem;
13 | import com.mobitant.bestfood.lib.EtcLib;
14 | import com.mobitant.bestfood.lib.GeoLib;
15 | import com.mobitant.bestfood.lib.MyLog;
16 | import com.mobitant.bestfood.lib.RemoteLib;
17 | import com.mobitant.bestfood.lib.StringLib;
18 | import com.mobitant.bestfood.remote.RemoteService;
19 | import com.mobitant.bestfood.remote.ServiceGenerator;
20 |
21 | import okhttp3.ResponseBody;
22 | import retrofit2.Call;
23 | import retrofit2.Callback;
24 | import retrofit2.Response;
25 |
26 | /**
27 | * 시작 액티비티이며 이 액티비티에서 사용자 정보를 조회해서
28 | * 메인 액티비티를 실행할 지, 프로필 액티비티를 실행할 지를 결정한다.
29 | */
30 | public class IndexActivity extends AppCompatActivity {
31 | private final String TAG = this.getClass().getSimpleName();
32 |
33 | Context context;
34 |
35 | /**
36 | * 레이아웃을 설정하고 인터넷에 연결되어 있는지를 확인한다.
37 | * 만약 인터넷에 연결되어 있지 않다면 showNoService() 메소드를 호출한다.
38 | * @param savedInstanceState 액티비티가 새로 생성되었을 경우, 이전 상태 값을 가지는 객체
39 | */
40 | @Override
41 | protected void onCreate(Bundle savedInstanceState) {
42 | super.onCreate(savedInstanceState);
43 | setContentView(R.layout.activity_index);
44 |
45 | context = this;
46 |
47 | if (!RemoteLib.getInstance().isConnected(context)) {
48 | showNoService();
49 | return;
50 | }
51 | }
52 |
53 | /**
54 | * 일정 시간(1.2초) 이후에 startTask() 메소드를 호출해서
55 | * 서버에서 사용자 정보를 조회한다.
56 | */
57 | @Override
58 | protected void onStart() {
59 | super.onStart();
60 |
61 | Handler mHandler = new Handler();
62 | mHandler.postDelayed(new Runnable() {
63 | @Override
64 | public void run() {
65 | startTask();
66 | }
67 | }, 1200);
68 | }
69 |
70 | /**
71 | * 현재 인터넷에 접속할 수 없기 때문에 서비스를 사용할 수 없다는 메시지와
72 | * 화면 종료 버튼을 보여준다.
73 | */
74 | private void showNoService() {
75 | TextView messageText = (TextView) findViewById(R.id.message);
76 | messageText.setVisibility(View.VISIBLE);
77 |
78 | Button closeButton = (Button) findViewById(R.id.close);
79 | closeButton.setOnClickListener(new View.OnClickListener() {
80 | @Override
81 | public void onClick(View v) {
82 | finish();
83 | }
84 | });
85 | closeButton.setVisibility(View.VISIBLE);
86 | }
87 |
88 | /**
89 | * 현재 폰의 전화번호와 동일한 사용자 정보를 조회할 수 있도록
90 | * selectMemberInfo() 메소드를 호출한다.
91 | * 그리고 setLastKnownLocation() 메소드를 호출해서 현재 위치 정보를 설정한다.
92 | */
93 | public void startTask() {
94 | String phone = EtcLib.getInstance().getPhoneNumber(this);
95 |
96 | selectMemberInfo(phone);
97 | GeoLib.getInstance().setLastKnownLocation(this);
98 | }
99 |
100 | /**
101 | * 리트로핏을 활용해서 서버로부터 사용자 정보를 조회한다.
102 | * 사용자 정보를 조회했다면 setMemberInfoItem() 메소드를 호출하고
103 | * 그렇지 않다면 goProfileActivity() 메소드를 호출한다.
104 | *
105 | * @param phone 폰의 전화번호
106 | */
107 | public void selectMemberInfo(String phone) {
108 | RemoteService remoteService = ServiceGenerator.createService(RemoteService.class);
109 |
110 | Call call = remoteService.selectMemberInfo(phone);
111 | call.enqueue(new Callback() {
112 | @Override
113 | public void onResponse(Call call, Response response) {
114 | MemberInfoItem item = response.body();
115 |
116 | if (response.isSuccessful() && !StringLib.getInstance().isBlank(item.name)) {
117 | MyLog.d(TAG, "success " + response.body().toString());
118 | setMemberInfoItem(item);
119 | } else {
120 | MyLog.d(TAG, "not success");
121 | goProfileActivity(item);
122 | }
123 | }
124 |
125 | @Override
126 | public void onFailure(Call call, Throwable t) {
127 | MyLog.d(TAG, "no internet connectivity");
128 | MyLog.d(TAG, t.toString());
129 | }
130 | });
131 | }
132 |
133 | /**
134 | * 전달받은 MemberInfoItem을 Application 객체에 저장한다.
135 | * 그리고 startMain() 메소드를 호출한다.
136 | *
137 | * @param item 사용자 정보
138 | */
139 | private void setMemberInfoItem(MemberInfoItem item) {
140 | ((MyApp) getApplicationContext()).setMemberInfoItem(item);
141 |
142 | startMain();
143 | }
144 |
145 | /**
146 | * MainActivity를 실행하고 현재 액티비티를 종료한다.
147 | */
148 | public void startMain() {
149 | Intent intent = new Intent(IndexActivity.this, MainActivity.class);
150 | startActivity(intent);
151 |
152 | finish();
153 | }
154 |
155 | /**
156 | * 사용자 정보를 조회하지 못했다면 insertMemberPhone() 메소드를 통해
157 | * 전화번호를 서버에 저장하고 MainActivity를 실행한 후 ProfileActivity를 실행한다.
158 | * 그리고 현재 액티비티를 종료한다.
159 | *
160 | * @param item 사용자 정보
161 | */
162 | private void goProfileActivity(MemberInfoItem item) {
163 | if (item == null || item.seq <= 0) {
164 | insertMemberPhone();
165 | }
166 |
167 | Intent intent = new Intent(IndexActivity.this, MainActivity.class);
168 | startActivity(intent);
169 |
170 | Intent intent2 = new Intent(this, ProfileActivity.class);
171 | startActivity(intent2);
172 |
173 | finish();
174 | }
175 |
176 | /**
177 | * 폰의 전화번호를 서버에 저장한다.
178 | */
179 | private void insertMemberPhone() {
180 | String phone = EtcLib.getInstance().getPhoneNumber(context);
181 | RemoteService remoteService =
182 | ServiceGenerator.createService(RemoteService.class);
183 |
184 | Call call = remoteService.insertMemberPhone(phone);
185 | call.enqueue(new Callback() {
186 | @Override
187 | public void onResponse(Call call, Response response) {
188 | if (response.isSuccessful()) {
189 | MyLog.d(TAG, "success insert id " + response.body().toString());
190 | } else {
191 | int statusCode = response.code();
192 |
193 | ResponseBody errorBody = response.errorBody();
194 |
195 | MyLog.d(TAG, "fail " + statusCode + errorBody.toString());
196 | }
197 | }
198 |
199 | @Override
200 | public void onFailure(Call call, Throwable t) {
201 | MyLog.d(TAG, "no internet connectivity");
202 | }
203 | });
204 | }
205 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood;
2 |
3 | import android.os.Bundle;
4 | import android.support.design.widget.NavigationView;
5 | import android.support.v4.view.GravityCompat;
6 | import android.support.v4.widget.DrawerLayout;
7 | import android.support.v7.app.ActionBarDrawerToggle;
8 | import android.support.v7.app.AppCompatActivity;
9 | import android.support.v7.widget.Toolbar;
10 | import android.view.MenuItem;
11 | import android.view.View;
12 | import android.widget.TextView;
13 |
14 | import com.mobitant.bestfood.item.MemberInfoItem;
15 | import com.mobitant.bestfood.lib.GoLib;
16 | import com.mobitant.bestfood.lib.StringLib;
17 | import com.mobitant.bestfood.remote.RemoteService;
18 | import com.squareup.picasso.Picasso;
19 |
20 | import de.hdodenhof.circleimageview.CircleImageView;
21 |
22 | /**
23 | * 맛집 정보앱의 핵심 액티비티이며 왼쪽에 네비게이션 뷰를 가지며
24 | * 다양한 프래그먼트를 보여주는 컨테이너 역할을 한다.
25 | */
26 | public class MainActivity extends AppCompatActivity
27 | implements NavigationView.OnNavigationItemSelectedListener {
28 | private final String TAG = getClass().getSimpleName();
29 |
30 | MemberInfoItem memberInfoItem;
31 | DrawerLayout drawer;
32 | View headerLayout;
33 |
34 | CircleImageView profileIconImage;
35 |
36 | /**
37 | * 액티비티와 네비게이션 뷰를 설정하고 BestFoodListFragment를 화면에 보여준다.
38 | * @param savedInstanceState 액티비티가 새로 생성되었을 경우, 이전 상태 값을 가지는 객체
39 | */
40 | @Override
41 | protected void onCreate(Bundle savedInstanceState) {
42 | super.onCreate(savedInstanceState);
43 | setContentView(R.layout.activity_main);
44 |
45 | memberInfoItem = ((MyApp)getApplication()).getMemberInfoItem();
46 |
47 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
48 | setSupportActionBar(toolbar);
49 |
50 | drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
51 |
52 | ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
53 | this, drawer, toolbar, R.string.navigation_drawer_open,
54 | R.string.navigation_drawer_close);
55 | drawer.addDrawerListener(toggle);
56 | toggle.syncState();
57 |
58 | NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
59 | navigationView.setNavigationItemSelectedListener(this);
60 |
61 | headerLayout = navigationView.getHeaderView(0);
62 |
63 | GoLib.getInstance()
64 | .goFragment(getSupportFragmentManager(), R.id.content_main,
65 | BestFoodListFragment.newInstance());
66 |
67 |
68 | }
69 |
70 | /**
71 | * 프로필 정보는 별도 액티비티에서 변경될 수 있으므로
72 | * 변경을 바로 감지하기 위해 화면이 새로 보여질 대마다 setProfileView() 를 호출한다.
73 | */
74 | @Override
75 | protected void onResume() {
76 | super.onResume();
77 |
78 | setProfileView();
79 | }
80 |
81 | /**
82 | * 프로필 이미지와 프로필 이름을 설정한다.
83 | */
84 | private void setProfileView() {
85 | profileIconImage = (CircleImageView) headerLayout.findViewById(R.id.profile_icon);
86 | profileIconImage.setOnClickListener(new View.OnClickListener() {
87 | @Override
88 | public void onClick(View v) {
89 | drawer.closeDrawer(GravityCompat.START);
90 | GoLib.getInstance().goProfileActivity(MainActivity.this);
91 | }
92 | });
93 |
94 | if (StringLib.getInstance().isBlank(memberInfoItem.memberIconFilename)) {
95 | Picasso.with(this).load(R.drawable.ic_person).into(profileIconImage);
96 | } else {
97 | Picasso.with(this)
98 | .load(RemoteService.MEMBER_ICON_URL + memberInfoItem.memberIconFilename)
99 | .into(profileIconImage);
100 | }
101 |
102 | TextView nameText = (TextView) headerLayout.findViewById(R.id.name);
103 |
104 | if (memberInfoItem.name == null || memberInfoItem.name.equals("")) {
105 | nameText.setText(R.string.name_need);
106 | } else {
107 | nameText.setText(memberInfoItem.name);
108 | }
109 | }
110 |
111 | /**
112 | * 폰에서 뒤로가기 버튼을 클릭했을 때 호출하는 메소드이며
113 | * 네비게이션 메뉴가 보여진 상태일 경우, 네비게이션 메뉴를 닫는다.
114 | */
115 | @Override
116 | public void onBackPressed() {
117 | if (drawer.isDrawerOpen(GravityCompat.START)) {
118 | drawer.closeDrawer(GravityCompat.START);
119 | } else {
120 | super.onBackPressed();
121 | }
122 | }
123 |
124 | /**
125 | * 네비게이션 메뉴를 클릭했을 때 호출되는 메소드
126 | * @param item 메뉴 아이템 객체
127 | * @return 메뉴 클릭 이벤트의 처리 여부
128 | */
129 | @SuppressWarnings("StatementWithEmptyBody")
130 | @Override
131 | public boolean onNavigationItemSelected(MenuItem item) {
132 | int id = item.getItemId();
133 |
134 | if (id == R.id.nav_list) {
135 | GoLib.getInstance().goFragment(getSupportFragmentManager(),
136 | R.id.content_main, BestFoodListFragment.newInstance());
137 |
138 | } else if (id == R.id.nav_map) {
139 | GoLib.getInstance().goFragment(getSupportFragmentManager(),
140 | R.id.content_main, BestFoodMapFragment.newInstance());
141 |
142 | } else if (id == R.id.nav_keep) {
143 | GoLib.getInstance().goFragment(getSupportFragmentManager(),
144 | R.id.content_main, BestFoodKeepFragment.newInstance());
145 |
146 | } else if (id == R.id.nav_register) {
147 | GoLib.getInstance().goBestFoodRegisterActivity(this);
148 |
149 | } else if (id == R.id.nav_profile) {
150 | GoLib.getInstance().goProfileActivity(this);
151 | }
152 |
153 | drawer.closeDrawer(GravityCompat.START);
154 | return true;
155 | }
156 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/MyApp.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood;
2 |
3 | import android.app.Application;
4 | import android.os.StrictMode;
5 |
6 | import com.mobitant.bestfood.item.FoodInfoItem;
7 | import com.mobitant.bestfood.item.MemberInfoItem;
8 |
9 | /**
10 | * 앱 전역에서 사용할 수 있는 클래스
11 | */
12 | public class MyApp extends Application {
13 | private MemberInfoItem memberInfoItem;
14 | private FoodInfoItem foodInfoItem;
15 |
16 | @Override
17 | public void onCreate() {
18 | super.onCreate();
19 |
20 | // FileUriExposedException 문제를 해결하기 위한 코드
21 | // 관련 설명은 책의 [참고] 페이지 참고
22 | StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
23 | StrictMode.setVmPolicy(builder.build());
24 | }
25 |
26 | public MemberInfoItem getMemberInfoItem() {
27 | if (memberInfoItem == null) memberInfoItem = new MemberInfoItem();
28 |
29 | return memberInfoItem;
30 | }
31 |
32 | public void setMemberInfoItem(MemberInfoItem item) {
33 | this.memberInfoItem = item;
34 | }
35 |
36 | public int getMemberSeq() {
37 | return memberInfoItem.seq;
38 | }
39 |
40 | public void setFoodInfoItem(FoodInfoItem foodInfoItem) {
41 | this.foodInfoItem = foodInfoItem;
42 | }
43 |
44 | public FoodInfoItem getFoodInfoItem() {
45 | return foodInfoItem;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/PermissionActivity.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood;
2 |
3 | import android.Manifest;
4 | import android.content.DialogInterface;
5 | import android.content.Intent;
6 | import android.content.pm.PackageManager;
7 | import android.net.Uri;
8 | import android.os.Build;
9 | import android.os.Bundle;
10 | import android.provider.Settings;
11 | import android.support.v4.app.ActivityCompat;
12 | import android.support.v4.content.ContextCompat;
13 | import android.support.v7.app.AlertDialog;
14 | import android.support.v7.app.AppCompatActivity;
15 |
16 | import com.mobitant.bestfood.lib.MyToast;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | /**
22 | * 앱을 실행할 때 필요한 권한을 처리하기 위한 액티비티
23 | */
24 | public class PermissionActivity extends AppCompatActivity {
25 | private static final int PERMISSION_MULTI_CODE = 100;
26 |
27 | /**
28 | * 화면을 구성하고 SDK 버전과 권한에 따른 처리를 한다.
29 | * @param savedInstanceState 액티비티가 새로 생성되었을 경우, 이전 상태 값을 가지는 객체
30 | */
31 | @Override
32 | protected void onCreate(Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 | setContentView(R.layout.activity_permission);
35 |
36 | if (Build.VERSION.SDK_INT < 23) {
37 | goIndexActivity();
38 | } else {
39 | if (checkAndRequestPermissions()) {
40 | goIndexActivity();
41 | }
42 | }
43 | }
44 |
45 | /**
46 | * 권한을 확인하고 권한이 부여되어 있지 않다면 권한을 요청한다.
47 | * @return 필요한 권한이 모두 부여되었다면 true, 그렇지 않다면 false
48 | */
49 | private boolean checkAndRequestPermissions() {
50 | String [] permissions = new String[]{
51 | Manifest.permission.CAMERA,
52 | Manifest.permission.READ_PHONE_STATE,
53 | Manifest.permission.ACCESS_FINE_LOCATION
54 | };
55 |
56 | List listPermissionsNeeded = new ArrayList<>();
57 |
58 | for (String permission:permissions) {
59 | if (ContextCompat.checkSelfPermission(this,permission )
60 | != PackageManager.PERMISSION_GRANTED){
61 | listPermissionsNeeded.add(permission);
62 | }
63 | }
64 |
65 | if (!listPermissionsNeeded.isEmpty()) {
66 | ActivityCompat.requestPermissions(this,
67 | listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]),
68 | PERMISSION_MULTI_CODE);
69 | return false;
70 | }
71 |
72 | return true;
73 | }
74 |
75 | /**
76 | * 권한 요청 결과를 받는 메소드
77 | * @param requestCode 요청 코드
78 | * @param permissions 권한 종류
79 | * @param grantResults 권한 결과
80 | */
81 | @Override
82 | public void onRequestPermissionsResult(int requestCode, String[] permissions,
83 | int[] grantResults) {
84 | if (grantResults.length == 0) return;
85 |
86 | switch (requestCode) {
87 | case PERMISSION_MULTI_CODE:
88 | checkPermissionResult(permissions, grantResults);
89 |
90 | break;
91 | }
92 | }
93 |
94 | /**
95 | * 권한 처리 결과를 보고 인덱스 액티비티를 실행할 지,
96 | * 권한 설정 요청 다이얼로그를 보여줄 지를 결정한다.
97 | * 모든 권한이 승인되었을 경우에는 goIndexActivity() 메소드를 호출한다.
98 | * @param permissions 권한 종류
99 | * @param grantResults 권한 부여 결과
100 | */
101 | private void checkPermissionResult(String[] permissions, int[] grantResults) {
102 | boolean isAllGranted = true;
103 |
104 | for (int i = 0; i < permissions.length; i++) {
105 | if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
106 | isAllGranted = false;
107 | }
108 | }
109 |
110 | //권한이 부여되었다면
111 | if (isAllGranted) {
112 | goIndexActivity();
113 |
114 | //권한이 부여되어 있지 않다면
115 | } else {
116 | showPermissionDialog();
117 | }
118 | }
119 |
120 | /**
121 | * 인덱스 액티비티를 실행하고 현재 액티비티를 종료한다.
122 | */
123 | private void goIndexActivity() {
124 | Intent intent = new Intent(this, IndexActivity.class);
125 | startActivity(intent);
126 |
127 | finish();
128 | }
129 |
130 | /**
131 | * 권한 설정 화면으로 이동할 지를 선택하는 다이얼로그를 보여준다.
132 | */
133 | private void showPermissionDialog() {
134 | AlertDialog.Builder dialog = new AlertDialog.Builder(this);
135 | dialog.setTitle(R.string.permission_setting_title);
136 | dialog.setMessage(R.string.permission_setting_message);
137 | dialog.setPositiveButton(R.string.setting, new DialogInterface.OnClickListener() {
138 | public void onClick(DialogInterface dialog, int which) {
139 | dialog.cancel();
140 | MyToast.s(PermissionActivity.this, R.string.permission_setting_restart);
141 | PermissionActivity.this.finish();
142 |
143 | goAppSettingActivity();
144 | }
145 | });
146 | dialog.setNegativeButton(R.string.cancel,new DialogInterface.OnClickListener() {
147 | public void onClick(DialogInterface dialog, int which) {
148 | dialog.cancel();
149 | PermissionActivity.this.finish();
150 | }
151 | });
152 | dialog.show();
153 | }
154 |
155 | /**
156 | * 권한을 설정할 수 있는 설정 액티비티를 실행한다.
157 | */
158 | private void goAppSettingActivity() {
159 | Intent intent = new Intent();
160 | intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
161 | Uri uri = Uri.fromParts("package", getPackageName(), null);
162 | intent.setData(uri);
163 | startActivity(intent);
164 | }
165 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/ProfileIconActivity.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.graphics.Bitmap;
6 | import android.net.Uri;
7 | import android.os.Bundle;
8 | import android.provider.MediaStore;
9 | import android.support.v7.app.ActionBar;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.support.v7.widget.Toolbar;
12 | import android.view.Menu;
13 | import android.view.MenuItem;
14 | import android.view.View;
15 | import android.widget.Button;
16 | import android.widget.ImageView;
17 |
18 | import com.mobitant.bestfood.item.MemberInfoItem;
19 | import com.mobitant.bestfood.lib.FileLib;
20 | import com.mobitant.bestfood.lib.MyLog;
21 | import com.mobitant.bestfood.lib.RemoteLib;
22 | import com.mobitant.bestfood.lib.StringLib;
23 | import com.mobitant.bestfood.remote.RemoteService;
24 | import com.squareup.picasso.Picasso;
25 |
26 | import java.io.File;
27 |
28 | /**
29 | * 프로필 아이콘을 등록하는 액티비티
30 | */
31 | public class ProfileIconActivity extends AppCompatActivity implements View.OnClickListener {
32 | private final String TAG = getClass().getSimpleName();
33 |
34 | private static final int PICK_FROM_CAMERA = 0;
35 | private static final int PICK_FROM_ALBUM = 1;
36 | private static final int CROP_FROM_CAMERA = 2;
37 | private static final int CROP_FROM_ALBUM = 3;
38 |
39 | Context context;
40 |
41 | ImageView profileIconImage;
42 |
43 | MemberInfoItem memberInfoItem;
44 |
45 | File profileIconFile;
46 | String profileIconFilename;
47 |
48 | /**
49 | * 액티비티를 생성하고 화면을 구성한다.
50 | * @param savedInstanceState 액티비티가 새로 생성되었을 경우, 이전 상태 값을 가지는 객체
51 | */
52 | @Override
53 | protected void onCreate(Bundle savedInstanceState) {
54 | super.onCreate(savedInstanceState);
55 | setContentView(R.layout.activity_profile_icon);
56 |
57 | context = this;
58 |
59 | memberInfoItem = ((MyApp) getApplication()).getMemberInfoItem();
60 |
61 | setToolbar();
62 | setView();
63 | setProfileIcon();
64 | }
65 |
66 | /**
67 | * 액티비티 툴바를 설정한다.
68 | */
69 | private void setToolbar() {
70 | final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
71 | setSupportActionBar(toolbar);
72 | final ActionBar actionBar = getSupportActionBar();
73 |
74 | if (actionBar != null) {
75 | actionBar.setDisplayHomeAsUpEnabled(true);
76 | actionBar.setTitle(R.string.profile_setting);
77 | }
78 | }
79 |
80 | /**
81 | * 액티비티 화면을 설정한다.
82 | */
83 | public void setView() {
84 | profileIconImage = (ImageView) findViewById(R.id.profile_icon);
85 |
86 | Button albumButton = (Button) findViewById(R.id.album);
87 | albumButton.setOnClickListener(this);
88 |
89 | Button cameraButton = (Button) findViewById(R.id.camera);
90 | cameraButton.setOnClickListener(this);
91 | }
92 |
93 | /**
94 | * 프로필 아이콘을 설정한다.
95 | */
96 | private void setProfileIcon() {
97 | MyLog.d(TAG, "onResume " +
98 | RemoteService.MEMBER_ICON_URL + memberInfoItem.memberIconFilename);
99 |
100 | if (StringLib.getInstance().isBlank(memberInfoItem.memberIconFilename)) {
101 | Picasso.with(this).load(R.drawable.ic_person).into(profileIconImage);
102 | } else {
103 | Picasso.with(this)
104 | .load(RemoteService.MEMBER_ICON_URL + memberInfoItem.memberIconFilename)
105 | .into(profileIconImage);
106 | }
107 | }
108 |
109 | /**
110 | * 사용자가 선택한 프로필 아이콘을 저장할 파일 이름을 설정한다.
111 | */
112 | private void setProfileIconFile() {
113 | profileIconFilename = memberInfoItem.seq + "_" + String.valueOf(System.currentTimeMillis());
114 |
115 | profileIconFile = FileLib.getInstance().getProfileIconFile(context, profileIconFilename);
116 | }
117 |
118 | /**
119 | * 프로필 아이콘을 설정하기 위해 선택할 수 있는 앨범이나 카메라 버튼의 클릭 이벤트를 처리한다.
120 | * @param v 클릭한 뷰 객체
121 | */
122 | @Override
123 | public void onClick(View v) {
124 | setProfileIconFile();
125 |
126 | if (v.getId() == R.id.album) {
127 | getImageFromAlbum();
128 |
129 | } else if (v.getId() == R.id.camera) {
130 | getImageFromCamera();
131 | }
132 | }
133 |
134 | /**
135 | * 오른쪽 상단 메뉴를 구성한다.
136 | * 닫기 메뉴만이 설정되어 있는 menu_close.xml를 지정한다.
137 | * @param menu 메뉴 객체
138 | * @return 메뉴를 보여준다면 true, 보여주지 않는다면 false
139 | */
140 | @Override
141 | public boolean onCreateOptionsMenu(Menu menu) {
142 | getMenuInflater().inflate(R.menu.menu_close, menu);
143 | return true;
144 | }
145 |
146 | /**
147 | * 왼쪽 화살표 메뉴(android.R.id.home)를 클릭했을 때와
148 | * 오른쪽 상단 닫기 메뉴를 클릭했을 때의 동작을 지정한다.
149 | * 여기서는 모든 버튼이 액티비티를 종료한다.
150 | * @param item 메뉴 아이템 객체
151 | * @return 메뉴를 처리했다면 true, 그렇지 않다면 false
152 | */
153 | @Override
154 | public boolean onOptionsItemSelected(MenuItem item) {
155 | switch (item.getItemId()) {
156 | case android.R.id.home:
157 | finish();
158 | break;
159 |
160 | case R.id.action_close:
161 | finish();
162 | break;
163 | }
164 |
165 | return true;
166 | }
167 |
168 | /**
169 | * 카메라 앱을 실행해서 이미지를 촬영한다.
170 | */
171 | private void getImageFromCamera() {
172 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
173 | intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(profileIconFile));
174 | startActivityForResult(intent, PICK_FROM_CAMERA);
175 | }
176 |
177 | /**
178 | * 카메라 앨범앱을 실행해서 이미지를 선택한다.
179 | */
180 | private void getImageFromAlbum() {
181 | Intent intent = new Intent(Intent.ACTION_PICK);
182 | intent.setType(MediaStore.Images.Media.CONTENT_TYPE);
183 | startActivityForResult(intent, PICK_FROM_ALBUM);
184 | }
185 |
186 | /**
187 | * 이미지를 자르기 위한 Intent를 생성해서 반환한다.
188 | * @param inputUri 이미지를 자르기전 Uri
189 | * @param outputUri 이미지를 자른 결과 파일 Uri
190 | * @return 이미지를 자르기 위한 인텐트
191 | */
192 | private Intent getCropIntent(Uri inputUri, Uri outputUri) {
193 | Intent intent = new Intent("com.android.camera.action.CROP");
194 | intent.setDataAndType(inputUri, "image/*");
195 | intent.putExtra("aspectX", 1);
196 | intent.putExtra("aspectY", 1);
197 | intent.putExtra("outputX", 200);
198 | intent.putExtra("outputY", 200);
199 | intent.putExtra("scale", true);
200 | intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
201 | intent.putExtra("outputFormat", Bitmap.CompressFormat.PNG.toString());
202 |
203 | return intent;
204 | }
205 |
206 | /**
207 | * 카메라에서 촬영한 이미지를 프로필 아이콘에 사용할 크기로 자른다.
208 | */
209 | private void cropImageFromCamera() {
210 | Uri uri = Uri.fromFile(profileIconFile);
211 | Intent intent = getCropIntent(uri, uri);
212 | startActivityForResult(intent, CROP_FROM_CAMERA);
213 | }
214 |
215 | /**
216 | * 카메라 앨범에서 선택한 이미지를 프로필 아이콘에 사용할 크기로 자른다.
217 | */
218 | private void cropImageFromAlbum(Uri inputUri) {
219 | Uri outputUri = Uri.fromFile(profileIconFile);
220 |
221 | MyLog.d(TAG, "startPickFromAlbum uri " + inputUri.toString());
222 | Intent intent = getCropIntent(inputUri, outputUri);
223 | startActivityForResult(intent, CROP_FROM_ALBUM);
224 | }
225 |
226 | /**
227 | * startActivityForResult() 메소드로 호출한 액티비티의 결과를 처리한다.
228 | * @param requestCode 액티비티를 실행하면서 전달한 요청 코드
229 | * @param resultCode 실행한 액티비티가 설정한 결과 코드
230 | * @param intent 결과 데이터
231 | */
232 | @Override
233 | protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
234 | MyLog.d(TAG, "onActivityResult " + intent);
235 |
236 | if (resultCode != RESULT_OK) return;
237 |
238 | if (requestCode == PICK_FROM_CAMERA) {
239 | cropImageFromCamera();
240 |
241 | } else if (requestCode == CROP_FROM_CAMERA) {
242 | Picasso.with(this).load(profileIconFile).into(profileIconImage);
243 | uploadProfileIcon();
244 |
245 | } else if (requestCode == PICK_FROM_ALBUM && intent != null) {
246 | Uri dataUri = intent.getData();
247 | if (dataUri != null) {
248 | cropImageFromAlbum(dataUri);
249 | }
250 | } else if (requestCode == CROP_FROM_ALBUM && intent != null) {
251 | Picasso.with(this).load(profileIconFile).into(profileIconImage);
252 | uploadProfileIcon();
253 | }
254 | }
255 |
256 | /**
257 | * 프로필 아이콘을 서버에 업로드한다.
258 | */
259 | private void uploadProfileIcon() {
260 | RemoteLib.getInstance().uploadMemberIcon(memberInfoItem.seq, profileIconFile);
261 |
262 | memberInfoItem.memberIconFilename = profileIconFilename + ".png";
263 | }
264 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/adapter/InfoListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.adapter;
2 |
3 | import android.content.Context;
4 | import android.os.Handler;
5 | import android.os.Message;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.ImageView;
11 | import android.widget.TextView;
12 |
13 | import com.mobitant.bestfood.Constant;
14 | import com.mobitant.bestfood.MyApp;
15 | import com.mobitant.bestfood.R;
16 | import com.mobitant.bestfood.item.FoodInfoItem;
17 | import com.mobitant.bestfood.item.MemberInfoItem;
18 | import com.mobitant.bestfood.lib.DialogLib;
19 | import com.mobitant.bestfood.lib.GoLib;
20 | import com.mobitant.bestfood.lib.MyLog;
21 | import com.mobitant.bestfood.lib.StringLib;
22 | import com.mobitant.bestfood.remote.RemoteService;
23 | import com.squareup.picasso.Picasso;
24 |
25 | import java.util.ArrayList;
26 |
27 | /**
28 | * 맛집 정보 리스트의 아이템을 처리하는 어댑터
29 | */
30 | public class InfoListAdapter extends RecyclerView.Adapter {
31 | private final String TAG = this.getClass().getSimpleName();
32 |
33 | private Context context;
34 | private int resource;
35 | private ArrayList itemList;
36 | private MemberInfoItem memberInfoItem;
37 |
38 | /**
39 | * 어댑터 생성자
40 | * @param context 컨텍스트 객체
41 | * @param resource 아이템을 보여주기 위해 사용할 리소스 아이디
42 | * @param itemList 아이템 리스트
43 | */
44 | public InfoListAdapter(Context context, int resource, ArrayList itemList) {
45 | this.context = context;
46 | this.resource = resource;
47 | this.itemList = itemList;
48 |
49 | memberInfoItem = ((MyApp) context.getApplicationContext()).getMemberInfoItem();
50 | }
51 |
52 | /**
53 | * 특정 아이템의 변경사항을 적용하기 위해 기본 아이템을 새로운 아이템으로 변경한다.
54 | * @param newItem 새로운 아이템
55 | */
56 | public void setItem(FoodInfoItem newItem) {
57 | for (int i=0; i < itemList.size(); i++) {
58 | FoodInfoItem item = itemList.get(i);
59 |
60 | if (item.seq == newItem.seq) {
61 | itemList.set(i, newItem);
62 | notifyItemChanged(i);
63 | break;
64 | }
65 | }
66 | }
67 |
68 | /**
69 | * 현재 아이템 리스트에 새로운 아이템 리스트를 추가한다.
70 | * @param itemList 새로운 아이템 리스트
71 | */
72 | public void addItemList(ArrayList itemList) {
73 | this.itemList.addAll(itemList);
74 | notifyDataSetChanged();
75 | }
76 |
77 | /**
78 | * 즐겨찾기 상태를 변경한다.
79 | * @param seq 맛집 정보 시퀀스
80 | * @param keep 즐겨찾기 추가 유무
81 | */
82 | private void changeItemKeep(int seq, boolean keep) {
83 | for (int i=0; i < itemList.size(); i++) {
84 | if (itemList.get(i).seq == seq) {
85 | itemList.get(i).isKeep = keep;
86 | notifyItemChanged(i);
87 | break;
88 | }
89 | }
90 | }
91 |
92 | /**
93 | * 아이템 크기를 반환한다.
94 | * @return 아이템 크기
95 | */
96 | @Override
97 | public int getItemCount() {
98 | return this.itemList.size();
99 | }
100 |
101 | /**
102 | * 뷰홀더(ViewHolder)를 생성하기 위해 자동으로 호출된다.
103 | * @param parent 부모 뷰그룹
104 | * @param viewType 새로운 뷰의 뷰타입
105 | * @return 뷰홀더 객체
106 | */
107 | @Override
108 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
109 | View v = LayoutInflater.from(parent.getContext()).inflate(resource, parent, false);
110 |
111 | return new ViewHolder(v);
112 | }
113 |
114 | /**
115 | * 뷰홀더(ViewHolder)와 아이템을 리스트 위치에 따라 연동한다.
116 | * @param holder 뷰홀더 객체
117 | * @param position 리스트 위치
118 | */
119 | @Override
120 | public void onBindViewHolder(ViewHolder holder, int position) {
121 | final FoodInfoItem item = itemList.get(position);
122 | MyLog.d(TAG, "getView " + item);
123 |
124 | if (item.isKeep) {
125 | holder.keep.setImageResource(R.drawable.ic_keep_on);
126 | } else {
127 | holder.keep.setImageResource(R.drawable.ic_keep_off);
128 | }
129 |
130 | holder.name.setText(item.name);
131 | holder.description.setText(StringLib.getInstance().getSubString(context,
132 | item.description, Constant.MAX_LENGTH_DESCRIPTION));
133 |
134 | setImage(holder.image, item.imageFilename);
135 |
136 | holder.itemView.setOnClickListener(new View.OnClickListener() {
137 | @Override
138 | public void onClick(View view) {
139 | GoLib.getInstance().goBestFoodInfoActivity(context, item.seq);
140 | }
141 | });
142 |
143 | holder.keep.setOnClickListener(new View.OnClickListener() {
144 | @Override
145 | public void onClick(View view) {
146 | if (item.isKeep) {
147 | DialogLib.getInstance().showKeepDeleteDialog(context,
148 | keepDeleteHandler, memberInfoItem.seq, item.seq);
149 | } else {
150 | DialogLib.getInstance().showKeepInsertDialog(context,
151 | keepInsertHandler, memberInfoItem.seq, item.seq);
152 | }
153 | }
154 | });
155 | }
156 |
157 | /**
158 | * 이미지를 설정한다.
159 | * @param imageView 이미지를 설정할 뷰
160 | * @param fileName 이미지 파일이름
161 | */
162 | private void setImage(ImageView imageView, String fileName) {
163 | if (StringLib.getInstance().isBlank(fileName)) {
164 | Picasso.with(context).load(R.drawable.bg_bestfood_drawer).into(imageView);
165 | } else {
166 | Picasso.with(context).load(RemoteService.IMAGE_URL + fileName).into(imageView);
167 | }
168 | }
169 |
170 | /**
171 | * 즐겨찾기 추가가 성공한 경우를 처리하는 핸들러
172 | */
173 | Handler keepInsertHandler = new Handler() {
174 | @Override
175 | public void handleMessage(Message msg) {
176 | super.handleMessage(msg);
177 |
178 | changeItemKeep(msg.what, true);
179 | }
180 | };
181 |
182 | /**
183 | * 즐겨찾기 삭제가 성공한 경우를 처리하는 핸들러
184 | */
185 | Handler keepDeleteHandler = new Handler() {
186 | @Override
187 | public void handleMessage(Message msg) {
188 | super.handleMessage(msg);
189 |
190 | changeItemKeep(msg.what, false);
191 | }
192 | };
193 |
194 | /**
195 | * 아이템을 보여주기 위한 뷰홀더 클래스
196 | */
197 | public class ViewHolder extends RecyclerView.ViewHolder {
198 | ImageView image;
199 | ImageView keep;
200 | TextView name;
201 | TextView description;
202 |
203 | public ViewHolder(View itemView) {
204 | super(itemView);
205 |
206 | image = (ImageView) itemView.findViewById(R.id.image);
207 | keep = (ImageView) itemView.findViewById(R.id.keep);
208 | name = (TextView) itemView.findViewById(R.id.name);
209 | description = (TextView) itemView.findViewById(R.id.description);
210 | }
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/adapter/KeepListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.adapter;
2 |
3 | import android.content.Context;
4 | import android.os.Handler;
5 | import android.os.Message;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.ImageView;
11 | import android.widget.TextView;
12 |
13 | import com.mobitant.bestfood.Constant;
14 | import com.mobitant.bestfood.R;
15 | import com.mobitant.bestfood.item.FoodInfoItem;
16 | import com.mobitant.bestfood.item.KeepItem;
17 | import com.mobitant.bestfood.lib.DialogLib;
18 | import com.mobitant.bestfood.lib.GoLib;
19 | import com.mobitant.bestfood.lib.MyLog;
20 | import com.mobitant.bestfood.lib.StringLib;
21 | import com.mobitant.bestfood.remote.RemoteService;
22 | import com.squareup.picasso.Picasso;
23 |
24 | import java.util.ArrayList;
25 |
26 | /**
27 | * 맛집 정보 즐겨찾기 리스트의 아이템을 처리하는 어댑터
28 | */
29 | public class KeepListAdapter extends RecyclerView.Adapter {
30 | private final String TAG = this.getClass().getSimpleName();
31 |
32 | private Context context;
33 | private int resource;
34 | private ArrayList itemList;
35 | private int memberSeq;
36 |
37 | /**
38 | * 어댑터 생성자
39 | * @param context 컨텍스트 객체
40 | * @param resource 아이템을 보여주기 위해 사용할 리소스 아이디
41 | * @param itemList 아이템 리스트
42 | */
43 | public KeepListAdapter(Context context, int resource, ArrayList itemList, int memberSeq) {
44 | this.context = context;
45 | this.resource = resource;
46 | this.itemList = itemList;
47 | this.memberSeq = memberSeq;
48 | }
49 |
50 | /**
51 | * 새로운 아이템 리스트를 설정한다.
52 | * @param itemList 새로운 아이템 리스트
53 | */
54 | public void setItemList(ArrayList itemList) {
55 | this.itemList = itemList;
56 | notifyDataSetChanged();
57 | }
58 |
59 | /**
60 | * 특정 아이템의 변경사항을 적용하기 위해 기본 아이템을 새로운 아이템으로 변경한다.
61 | * @param newItem 새로운 아이템
62 | */
63 | public void setItem(FoodInfoItem newItem) {
64 | for (int i=0; i < itemList.size(); i++) {
65 | KeepItem item = itemList.get(i);
66 |
67 | if (item.seq == newItem.seq && !newItem.isKeep) {
68 | itemList.remove(i);
69 | notifyItemChanged(i);
70 | break;
71 | }
72 | }
73 | }
74 |
75 | /**
76 | * 맛집 정보 시퀀스와 일치하는 아이템을 즐겨찾기 리스트에서 삭제한다.
77 | * @param seq
78 | */
79 | private void removeItem(int seq) {
80 | for (int i=0; i < itemList.size(); i++) {
81 | if (itemList.get(i).seq == seq) {
82 | itemList.remove(i);
83 | notifyItemChanged(i);
84 | break;
85 | }
86 | }
87 | }
88 |
89 | /**
90 | * 아이템 크기를 반환한다.
91 | * @return 아이템 크기
92 | */
93 | @Override
94 | public int getItemCount() {
95 | return this.itemList.size();
96 | }
97 |
98 | /**
99 | * 뷰홀더(ViewHolder)를 생성하기 위해 자동으로 호출된다.
100 | * @param parent 부모 뷰그룹
101 | * @param viewType 새로운 뷰의 뷰타입
102 | * @return 뷰홀더 객체
103 | */
104 | @Override
105 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
106 | View v = LayoutInflater.from(parent.getContext()).inflate(resource, parent, false);
107 |
108 | return new ViewHolder(v);
109 | }
110 |
111 | /**
112 | * 뷰홀더(ViewHolder)와 아이템을 리스트 위치에 따라 연동한다.
113 | * @param holder 뷰홀더 객체
114 | * @param position 리스트 위치
115 | */
116 | @Override
117 | public void onBindViewHolder(ViewHolder holder, int position) {
118 | final KeepItem item = itemList.get(position);
119 | MyLog.d(TAG, "getView " + item);
120 |
121 | if (item.isKeep) {
122 | holder.keep.setImageResource(R.drawable.ic_keep_on);
123 | } else {
124 | holder.keep.setImageResource(R.drawable.ic_keep_off);
125 | }
126 |
127 | holder.name.setText(item.name);
128 | holder.description.setText(
129 | StringLib.getInstance().getSubString(context,
130 | item.description, Constant.MAX_LENGTH_DESCRIPTION));
131 |
132 | setImage(holder.image, item.imageFilename);
133 |
134 | holder.itemView.setOnClickListener(new View.OnClickListener() {
135 | @Override
136 | public void onClick(View view) {
137 | GoLib.getInstance().goBestFoodInfoActivity(context, item.seq);
138 | }
139 | });
140 |
141 | holder.keep.setOnClickListener(new View.OnClickListener() {
142 | @Override
143 | public void onClick(View view) {
144 | DialogLib.getInstance().showKeepDeleteDialog(context, keepHandler, memberSeq, item.seq);
145 | }
146 | });
147 | }
148 | /**
149 | * 이미지를 설정한다.
150 | * @param imageView 이미지를 설정할 뷰
151 | * @param fileName 이미지 파일이름
152 | */
153 | private void setImage(ImageView imageView, String fileName) {
154 | MyLog.d(TAG, "setImage fileName " + fileName);
155 |
156 | if (StringLib.getInstance().isBlank(fileName)) {
157 | Picasso.with(context).load(R.drawable.bg_bestfood_drawer).into(imageView);
158 | } else {
159 | Picasso.with(context).load(RemoteService.IMAGE_URL + fileName).into(imageView);
160 | }
161 | }
162 |
163 | /**
164 | * 즐겨찾기 리스트에서 해당 아이템을 삭제하기 위한 핸들러
165 | */
166 | Handler keepHandler = new Handler() {
167 | @Override
168 | public void handleMessage(Message msg) {
169 | super.handleMessage(msg);
170 |
171 | removeItem(msg.what);
172 | }
173 | };
174 |
175 | /**
176 | * 아이템을 보여주기 위한 뷰홀더 클래스
177 | */
178 | public class ViewHolder extends RecyclerView.ViewHolder {
179 | ImageView image;
180 | ImageView keep;
181 | TextView name;
182 | TextView description;
183 |
184 | public ViewHolder(View itemView) {
185 | super(itemView);
186 |
187 | image = (ImageView) itemView.findViewById(R.id.image);
188 | keep = (ImageView) itemView.findViewById(R.id.keep);
189 | name = (TextView) itemView.findViewById(R.id.name);
190 | description = (TextView) itemView.findViewById(R.id.description);
191 | }
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/adapter/MapListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.adapter;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.ImageView;
9 | import android.widget.TextView;
10 |
11 | import com.mobitant.bestfood.Constant;
12 | import com.mobitant.bestfood.R;
13 | import com.mobitant.bestfood.item.FoodInfoItem;
14 | import com.mobitant.bestfood.lib.GoLib;
15 | import com.mobitant.bestfood.lib.MyLog;
16 | import com.mobitant.bestfood.lib.StringLib;
17 | import com.mobitant.bestfood.remote.RemoteService;
18 | import com.squareup.picasso.Picasso;
19 |
20 | import java.util.ArrayList;
21 |
22 | /**
23 | * 구글 지도 맵에서 맛집 정보 리스트의 아이템을 처리하는 어댑터
24 | */
25 | public class MapListAdapter extends RecyclerView.Adapter {
26 | private final String TAG = this.getClass().getSimpleName();
27 |
28 | private Context context;
29 | private int resource;
30 | private ArrayList itemList;
31 |
32 | /**
33 | * 어댑터 생성자
34 | * @param context 컨텍스트 객체
35 | * @param resource 아이템을 보여주기 위해 사용할 리소스 아이디
36 | * @param itemList 아이템 리스트
37 | */
38 | public MapListAdapter(Context context, int resource, ArrayList itemList) {
39 | this.context = context;
40 | this.resource = resource;
41 | this.itemList = itemList;
42 | }
43 |
44 | /**
45 | * 새로운 아이템 리스트를 설정한다.
46 | * @param itemList 새로운 아이템 리스트
47 | */
48 | public void setItemList(ArrayList itemList) {
49 | this.itemList = itemList;
50 | notifyDataSetChanged();
51 | }
52 |
53 | /**
54 | * 아이템 크기를 반환한다.
55 | * @return 아이템 크기
56 | */
57 | @Override
58 | public int getItemCount() {
59 | return this.itemList.size();
60 | }
61 |
62 | /**
63 | * 뷰홀더(ViewHolder)를 생성하기 위해 자동으로 호출된다.
64 | * @param parent 부모 뷰그룹
65 | * @param viewType 새로운 뷰의 뷰타입
66 | * @return 뷰홀더 객체
67 | */
68 | @Override
69 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
70 | View v = LayoutInflater.from(parent.getContext()).inflate(resource, parent, false);
71 |
72 | return new ViewHolder(v);
73 | }
74 |
75 | /**
76 | * 뷰홀더(ViewHolder)와 아이템을 리스트 위치에 따라 연동한다.
77 | * @param holder 뷰홀더 객체
78 | * @param position 리스트 위치
79 | */
80 | @Override
81 | public void onBindViewHolder(ViewHolder holder, int position) {
82 | final FoodInfoItem item = itemList.get(position);
83 |
84 | int meter = (int) item.userDistanceMeter;
85 |
86 | if (meter == 0) {
87 | holder.distanceMeter.setText("");
88 | } else if (meter < 1000) {
89 | holder.distanceMeter.setText(meter + context.getResources().getString(R.string.unit_m));
90 | } else {
91 | holder.distanceMeter.setText( (meter / 1000)
92 | + context.getResources().getString(R.string.unit_km));
93 | }
94 |
95 | holder.name.setText(item.name);
96 | holder.description.setText(StringLib.getInstance().getSubString(context,
97 | item.description, Constant.MAX_LENGTH_DESCRIPTION));
98 |
99 | setImage(holder.imageView, item.imageFilename);
100 |
101 | holder.itemView.setOnClickListener(new View.OnClickListener() {
102 | @Override
103 | public void onClick(View view) {
104 | GoLib.getInstance().goBestFoodInfoActivity(context, item.seq);
105 | }
106 | });
107 | }
108 |
109 | /**
110 | * 이미지를 설정한다.
111 | * @param imageView 이미지를 설정할 뷰
112 | * @param fileName 이미지 파일이름
113 | */
114 | private void setImage(ImageView imageView, String fileName) {
115 | MyLog.d(TAG, "setImage fileName " + fileName);
116 |
117 | if (StringLib.getInstance().isBlank(fileName)) {
118 | Picasso.with(context).load(R.drawable.bg_bestfood_drawer).into(imageView);
119 | } else {
120 | Picasso.with(context).load(RemoteService.IMAGE_URL + fileName).into(imageView);
121 | }
122 | }
123 |
124 | /**
125 | * 아이템을 보여주기 위한 뷰홀더 클래스
126 | */
127 | public class ViewHolder extends RecyclerView.ViewHolder {
128 | ImageView imageView;
129 | TextView distanceMeter;
130 | TextView name;
131 | TextView description;
132 |
133 | public ViewHolder(View itemView) {
134 | super(itemView);
135 |
136 | imageView = (ImageView) itemView.findViewById(R.id.image);
137 | distanceMeter = (TextView) itemView.findViewById(R.id.distance_meter);
138 | name = (TextView) itemView.findViewById(R.id.name);
139 | description = (TextView) itemView.findViewById(R.id.description);
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/custom/EndlessRecyclerViewScrollListener.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.custom;
2 |
3 | import android.support.v7.widget.GridLayoutManager;
4 | import android.support.v7.widget.LinearLayoutManager;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.support.v7.widget.StaggeredGridLayoutManager;
7 |
8 | /**
9 | * 리사이클러뷰에서 스크롤 할 경우, 데이터를 추가적으로 가지고 올 수 있도록 해주는 추상 클래스
10 | */
11 | public abstract class EndlessRecyclerViewScrollListener extends RecyclerView.OnScrollListener {
12 | //새로운 아이템을 로드하기 위한 현재 스크롤 위치 하단의 아이템 개수
13 | private int visibleThreshold = 5;
14 | // 로딩할 페이지 인덱스
15 | private int currentPage = 0;
16 | // 최근 로딩 후의 전체 아이템 개수
17 | private int previousTotalItemCount = 0;
18 | // 로딩하고 있는 중인지에 대한 상태
19 | private boolean loading = true;
20 | // 시작 페이지 인덱스
21 | private int startingPageIndex = 0;
22 |
23 | RecyclerView.LayoutManager layoutManager;
24 |
25 | /**
26 | * LinearLayoutManager를 위한 생성자
27 | * @param layoutManager LinearLayoutManager
28 | */
29 | public EndlessRecyclerViewScrollListener(LinearLayoutManager layoutManager) {
30 | this.layoutManager = layoutManager;
31 | }
32 |
33 | /**
34 | * GridLayoutManager를 위한 생성자
35 | * @param layoutManager GridLayoutManager
36 | */
37 | public EndlessRecyclerViewScrollListener(GridLayoutManager layoutManager) {
38 | this.layoutManager = layoutManager;
39 | visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
40 | }
41 |
42 | /**
43 | * StaggeredGridLayoutManager를 위한 생성자
44 | * @param layoutManager StaggeredGridLayoutManager
45 | */
46 | public EndlessRecyclerViewScrollListener(StaggeredGridLayoutManager layoutManager) {
47 | this.layoutManager = layoutManager;
48 | visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
49 | }
50 |
51 | /**
52 | * 현재 화면의 여러 스팬 중에서 가장 마지막에 보이는 아이템의 위치를 반환한다.
53 | * @param lastVisibleItemPositions 마지막에 보이는 아이템의 포지션 값들
54 | * @return 가장 마지막 아이템 위치
55 | */
56 | public int getLastVisibleItemPosition(int[] lastVisibleItemPositions) {
57 | int maxSize = 0;
58 | for (int i = 0; i < lastVisibleItemPositions.length; i++) {
59 | if (i == 0) {
60 | maxSize = lastVisibleItemPositions[i];
61 | }
62 | else if (lastVisibleItemPositions[i] > maxSize) {
63 | maxSize = lastVisibleItemPositions[i];
64 | }
65 | }
66 | return maxSize;
67 | }
68 |
69 | /**
70 | * 스크롤된 후에 호출되는 컬백 메소드이며 아이템을 추가로 로딩해야 하는지를 체크한다.
71 | * @param view 리사이클러뷰
72 | * @param dx 수평으로 스크롤된 양
73 | * @param dy 수직으로 스크롤된 양
74 | */
75 | @Override
76 | public void onScrolled(RecyclerView view, int dx, int dy) {
77 | int lastVisibleItemPosition = 0;
78 | int totalItemCount = layoutManager.getItemCount();
79 |
80 | if (layoutManager instanceof StaggeredGridLayoutManager) {
81 | int[] lastVisibleItemPositions =
82 | ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(null);
83 | lastVisibleItemPosition = getLastVisibleItemPosition(lastVisibleItemPositions);
84 | } else if (layoutManager instanceof GridLayoutManager) {
85 | lastVisibleItemPosition =
86 | ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
87 | } else if (layoutManager instanceof LinearLayoutManager) {
88 | lastVisibleItemPosition =
89 | ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
90 | }
91 |
92 | // 새로 로딩한 전체 아이템 개수가 이전에 설정된 전체 아이템 개수보다 작을 경우
93 | // 상태를 초기화한다. 이런 경우는 리사이클러뷰의 데이터가 초기화된 경우에 발생한다.
94 | if (totalItemCount < previousTotalItemCount) {
95 | this.currentPage = this.startingPageIndex;
96 | this.previousTotalItemCount = totalItemCount;
97 | if (totalItemCount == 0) {
98 | this.loading = true;
99 | }
100 | }
101 |
102 | // 현재 로딩중인 상태이고 새로 로딩한 전체 아이템 개수가 이전에 저장한 전체 아이템 개수보다
103 | // 크다면 로딩이 완료한 것으로 간주한다.
104 | if (loading && (totalItemCount > previousTotalItemCount)) {
105 | loading = false;
106 | previousTotalItemCount = totalItemCount;
107 | }
108 |
109 | // 로딩중이 아니고 화면에 보이는 마지막 아이템 위치에 visibleThreshold을 더한 값이
110 | // totalItemCount 보다 큰 경우 새로 로딩할 아이템이 있는 것으로 간주한다.
111 | // 이때 onLoadMore() 메소드가 호출된다.
112 | if (!loading && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) {
113 | currentPage++;
114 | onLoadMore(currentPage, totalItemCount, view);
115 | loading = true;
116 | }
117 | }
118 |
119 | /**
120 | * 아이템을 더 로딩하기 위한 메소드로서 해당 메소드를 직접 작성해야 한다.
121 | * @param page 로딩할 페이지
122 | * @param totalItemsCount 전체 아이템 개수
123 | * @param view 리사이클러뷰
124 | */
125 | public abstract void onLoadMore(int page, int totalItemsCount, RecyclerView view);
126 |
127 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/custom/WorkaroundMapFragment.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.custom;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.support.v4.content.ContextCompat;
6 | import android.view.LayoutInflater;
7 | import android.view.MotionEvent;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.FrameLayout;
11 |
12 | import com.google.android.gms.maps.SupportMapFragment;
13 |
14 | /**
15 | * 스크롤 내부의 구글맵의 스크롤을 정상적으로 동작시키기 위한 커스텀 클래스
16 | */
17 | public class WorkaroundMapFragment extends SupportMapFragment {
18 | private OnTouchListener mListener;
19 |
20 | /**
21 | * 뷰를 생성해서 반환한다.
22 | * @param layoutInflater 레이아웃 인플레이터 객체
23 | * @param viewGroup 뷰그룹
24 | * @param savedInstance 번들 객체
25 | * @return 생성한 뷰 객체
26 | */
27 | @Override
28 | public View onCreateView(LayoutInflater layoutInflater, ViewGroup viewGroup, Bundle savedInstance) {
29 | View layout = super.onCreateView(layoutInflater, viewGroup, savedInstance);
30 |
31 | TouchableWrapper frameLayout = new TouchableWrapper(getActivity());
32 |
33 | int bgColor = ContextCompat.getColor(getActivity(), android.R.color.transparent);
34 |
35 | frameLayout.setBackgroundColor(bgColor);
36 |
37 | ((ViewGroup) layout).addView(frameLayout,
38 | new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
39 | ViewGroup.LayoutParams.MATCH_PARENT));
40 |
41 | return layout;
42 | }
43 |
44 | /**
45 | * 터치 리스너를 설정한다.
46 | * @param listener
47 | */
48 | public void setListener(OnTouchListener listener) {
49 | mListener = listener;
50 | }
51 |
52 | /**
53 | * 터치 리스너 인터페이스
54 | */
55 | public interface OnTouchListener {
56 | void onTouch();
57 | }
58 |
59 | /**
60 | * 터치 가능한 영역을 처리하기 위한 래퍼 클래스
61 | */
62 | public class TouchableWrapper extends FrameLayout {
63 |
64 | public TouchableWrapper(Context context) {
65 | super(context);
66 | }
67 |
68 | @Override
69 | public boolean dispatchTouchEvent(MotionEvent event) {
70 | switch (event.getAction()) {
71 | case MotionEvent.ACTION_DOWN:
72 | mListener.onTouch();
73 | break;
74 | case MotionEvent.ACTION_UP:
75 | mListener.onTouch();
76 | break;
77 | }
78 | return super.dispatchTouchEvent(event);
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/item/FoodInfoItem.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.item;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | /**
6 | * 맛집 정보를 저장하는 객체
7 | */
8 | @org.parceler.Parcel
9 | public class FoodInfoItem {
10 | public int seq;
11 | @SerializedName("member_seq") public int memberSeq;
12 | public String name;
13 | public String tel;
14 | public String address;
15 | public double latitude;
16 | public double longitude;
17 | public String description;
18 | @SerializedName("reg_date") public String regDate;
19 | @SerializedName("mod_date") public String modDate;
20 | @SerializedName("user_distance_meter") public double userDistanceMeter;
21 | @SerializedName("is_keep") public boolean isKeep;
22 | @SerializedName("image_filename") public String imageFilename;
23 |
24 | @Override
25 | public String toString() {
26 | return "FoodInfoItem{" +
27 | "seq=" + seq +
28 | ", memberSeq=" + memberSeq +
29 | ", name='" + name + '\'' +
30 | ", tel='" + tel + '\'' +
31 | ", address='" + address + '\'' +
32 | ", latitude=" + latitude +
33 | ", longitude=" + longitude +
34 | ", description='" + description + '\'' +
35 | ", regDate='" + regDate + '\'' +
36 | ", modDate='" + modDate + '\'' +
37 | ", userDistanceMeter=" + userDistanceMeter +
38 | ", isKeep=" + isKeep +
39 | ", imageFilename='" + imageFilename + '\'' +
40 | '}';
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/item/GeoItem.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.item;
2 |
3 | import com.google.android.gms.maps.model.LatLng;
4 |
5 | /**
6 | * 위치 정보를 저장하는 객체
7 | */
8 | public class GeoItem {
9 | public static double knownLatitude;
10 | public static double knownLongitude;
11 |
12 | /**
13 | * 사용자의 위도, 경도 객체를 반환한다. 만약 사용자의 위치를 알 수 없다면 서울 위치를 반환한다.
14 | * @return LatLng 위도,경도 객체
15 | */
16 | public static LatLng getKnownLocation() {
17 | if (knownLatitude == 0 || knownLongitude == 0) {
18 | return new LatLng(37.566229, 126.977689);
19 | } else {
20 | return new LatLng(knownLatitude, knownLongitude);
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/item/ImageItem.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.item;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | /**
6 | * 맛집 이미지 정보를 저장하는 객체
7 | */
8 | public class ImageItem {
9 | @SerializedName("seq") public int seq;
10 | @SerializedName("info_seq") public int infoSeq;
11 | @SerializedName("file_name") public String fileName;
12 | @SerializedName("image_memo") public String imageMemo;
13 | @SerializedName("reg_date") public String regDate;
14 |
15 | @Override
16 | public String toString() {
17 | return "ImageItem{" +
18 | "seq=" + seq +
19 | ", infoSeq=" + infoSeq +
20 | ", fileName='" + fileName + '\'' +
21 | ", imageMemo='" + imageMemo + '\'' +
22 | ", regDate='" + regDate + '\'' +
23 | '}';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/item/KeepItem.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.item;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | /**
6 | * 즐겨찾기 정보를 저장하는 객체
7 | */
8 | public class KeepItem extends FoodInfoItem{
9 | @SerializedName("keep_seq") public String keepSeq;
10 | @SerializedName("keep_member_seq") public String keepMemberSeq;
11 | @SerializedName("keep_date") public String keepDate;
12 |
13 | @Override
14 | public String toString() {
15 | return "KeepItem{" +
16 | "keepSeq='" + keepSeq + '\'' +
17 | ", keepMemberSeq='" + keepMemberSeq + '\'' +
18 | ", keepDate='" + keepDate + '\'' +
19 | '}';
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/item/MemberInfoItem.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.item;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | /**
6 | * 사용자 정보를 저장하는 객체
7 | */
8 | public class MemberInfoItem {
9 | public int seq;
10 | public String phone;
11 | public String name;
12 | public String sextype;
13 | public String birthday;
14 | @SerializedName("member_icon_filename") public String memberIconFilename;
15 | @SerializedName("reg_date") public String regDate;
16 |
17 | @Override
18 | public String toString() {
19 | return "MemberInfoItem{" +
20 | "seq=" + seq +
21 | ", phone='" + phone + '\'' +
22 | ", name='" + name + '\'' +
23 | ", sextype='" + sextype + '\'' +
24 | ", birthday='" + birthday + '\'' +
25 | ", memberIconFilename='" + memberIconFilename + '\'' +
26 | ", regDate='" + regDate + '\'' +
27 | '}';
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/lib/BitmapLib.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.lib;
2 |
3 | import android.graphics.Bitmap;
4 | import android.os.Handler;
5 |
6 | import java.io.File;
7 | import java.io.FileOutputStream;
8 |
9 | /**
10 | * 비트맵 관련 라이브러리
11 | */
12 | public class BitmapLib {
13 | public static final String TAG = BitmapLib.class.getSimpleName();
14 | private volatile static BitmapLib instance;
15 |
16 | public static BitmapLib getInstance() {
17 | if (instance == null) {
18 | synchronized (BitmapLib.class) {
19 | if (instance == null) {
20 | instance = new BitmapLib();
21 | }
22 | }
23 | }
24 | return instance;
25 | }
26 |
27 | /**
28 | * 비트맵을 별도 스레드에서 파일로 저장한다.
29 | * @param handler 결과를 알려줄 핸들러
30 | * @param file 파일 객체
31 | * @param bitmap 비트맵 객체
32 | */
33 | public void saveBitmapToFileThread(final Handler handler, final File file,
34 | final Bitmap bitmap) {
35 | new Thread() {
36 | @Override
37 | public void run() {
38 | saveBitmapToFile(file, bitmap);
39 | handler.sendEmptyMessage(0);
40 | }
41 | }.start();
42 | }
43 |
44 | /**
45 | * 비트맵을 파일에 저장한다.
46 | * @param file 파일 객체
47 | * @param bitmap 비트맵 객체
48 | * @return 파일 저장 성공 여부
49 | */
50 | private boolean saveBitmapToFile(File file, Bitmap bitmap) {
51 | if (bitmap == null) return false;
52 |
53 | boolean save = false;
54 |
55 | FileOutputStream out = null;
56 | try {
57 | out = new FileOutputStream(file);
58 |
59 | bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
60 | save = true;
61 | } catch (Exception e) {
62 | save = false;
63 | } finally {
64 | try {
65 | out.close();
66 | } catch (Exception e) {
67 | }
68 | }
69 | return save;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/lib/DialogLib.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.lib;
2 |
3 | import android.app.AlertDialog;
4 | import android.content.Context;
5 | import android.content.DialogInterface;
6 | import android.os.Handler;
7 |
8 | import com.mobitant.bestfood.R;
9 |
10 | /**
11 | * 다이얼로그와 관련된 메소드로 구성된 라이브러리
12 | */
13 | public class DialogLib {
14 | public final String TAG = DialogLib.class.getSimpleName();
15 | private volatile static DialogLib instance;
16 |
17 | public static DialogLib getInstance() {
18 | if (instance == null) {
19 | synchronized (DialogLib.class) {
20 | if (instance == null) {
21 | instance = new DialogLib();
22 | }
23 | }
24 | }
25 | return instance;
26 | }
27 |
28 | /**
29 | * 즐겨찾기 추가 다이얼로그 화면을 보여준다.
30 | * @param context 컨텍스트 객체
31 | * @param handler 핸들러 객체
32 | * @param memberSeq 사용자 일련번호
33 | * @param infoSeq 맛집 정보 일련번호
34 | */
35 | public void showKeepInsertDialog(Context context, final Handler handler,
36 | final int memberSeq, final int infoSeq) {
37 | new AlertDialog.Builder(context)
38 | .setTitle(R.string.keep_insert)
39 | .setMessage(R.string.keep_insert_message)
40 | .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
41 | @Override
42 | public void onClick(DialogInterface dialog, int which) {
43 |
44 | }
45 | })
46 | .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
47 | @Override
48 | public void onClick(DialogInterface dialog, int which) {
49 | KeepLib.getInstance().insertKeep(handler, memberSeq, infoSeq);
50 | }
51 | })
52 | .show();
53 | }
54 |
55 | /**
56 | * 즐겨찾기 삭제 다이얼로그 화면을 보여준다.
57 | * @param context 컨텍스트 객체
58 | * @param handler 핸들러 객체
59 | * @param memberSeq 사용자 일련번호
60 | * @param infoSeq 맛집 정보 일련번호
61 | */
62 | public void showKeepDeleteDialog(Context context, final Handler handler,
63 | final int memberSeq, final int infoSeq) {
64 | new AlertDialog.Builder(context)
65 | .setTitle(R.string.keep_delete)
66 | .setMessage(R.string.keep_delete_message)
67 | .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
68 | @Override
69 | public void onClick(DialogInterface dialog, int which) {
70 |
71 | }
72 | })
73 | .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
74 | @Override
75 | public void onClick(DialogInterface dialog, int which) {
76 | KeepLib.getInstance().deleteKeep(handler, memberSeq, infoSeq);
77 | }
78 | })
79 | .show();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/lib/EtcLib.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.lib;
2 |
3 | import android.content.Context;
4 | import android.os.Build;
5 | import android.provider.Settings;
6 | import android.telephony.TelephonyManager;
7 |
8 | import java.util.Locale;
9 | import java.util.regex.Pattern;
10 |
11 | /**
12 | * 기타 라이브러리
13 | */
14 | public class EtcLib {
15 | public final String TAG = EtcLib.class.getSimpleName();
16 | private volatile static EtcLib instance;
17 |
18 | public static EtcLib getInstance() {
19 | if (instance == null) {
20 | synchronized (EtcLib.class) {
21 | if (instance == null) {
22 | instance = new EtcLib();
23 | }
24 | }
25 | }
26 | return instance;
27 | }
28 |
29 | /**
30 | * 현재 기기의 전화 번호를 반환한다(+82가 붙은 전화번호로 반환).
31 | * @param context 컨텍스트 객체
32 | * @return 전화번호 문자열
33 | */
34 | public String getPhoneNumber(Context context) {
35 |
36 | TelephonyManager tm =
37 | (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
38 | String number = tm.getLine1Number();
39 |
40 | if (number != null && !number.equals("") && number.length() >= 8) {
41 | if (Locale.getDefault().getCountry().equals("KR")) {
42 | if (number.startsWith("82")) {
43 | number = "+" + number;
44 | }
45 |
46 | if (number.startsWith("0")) {
47 | number = "+82" + number.substring(1, number.length());
48 | }
49 | }
50 |
51 | MyLog.d(TAG, "number " + number);
52 |
53 | } else {
54 | number = getDeviceId(context);
55 | }
56 |
57 | return number;
58 | }
59 |
60 | /**
61 | * 전화번호가 없을 경우 기기 아이디를 반환한다.
62 | * @param context 컨텍스트 객체
63 | * @return 기기 아이디 문자열
64 | */
65 | private String getDeviceId(Context context) {
66 | TelephonyManager tm =
67 | (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
68 |
69 | String tmDevice = tm.getDeviceId();
70 | String androidId =
71 | Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
72 | String serial = null;
73 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO) serial = Build.SERIAL;
74 |
75 | if (tmDevice != null) return "01" + tmDevice;
76 | if (androidId != null) return "02" + androidId;
77 | if (serial != null) return "03" + serial;
78 |
79 | return null;
80 | }
81 |
82 |
83 | /**
84 | * 전화번호가 유효한 자리수를 가지고 있는지를 체크한다.
85 | * @param number 전화번호 문자열
86 | * @return 유효한 전화번호일 경우 true, 그렇지 않으면 false
87 | */
88 | public boolean isValidPhoneNumber(String number) {
89 | if (number == null) {
90 | return false;
91 | } else {
92 | if (Pattern.matches("\\d{2}-\\d{3}-\\d{4}", number)
93 | || Pattern.matches("\\d{3}-\\d{3}-\\d{4}", number)
94 | || Pattern.matches("\\d{3}-\\d{4}-\\d{4}", number)
95 | || Pattern.matches("\\d{10}", number)
96 | || Pattern.matches("\\d{11}", number) ) {
97 | return true;
98 | } else {
99 | return false;
100 | }
101 | }
102 | }
103 |
104 | /**
105 | * 전화번호에 '-'를 붙여서 반환한다.
106 | * @param number 전화번호 문자열
107 | * @return 변경된 전화번호 문자열
108 | */
109 | public String getPhoneNumberText(String number) {
110 | String phoneText = "";
111 |
112 | if (StringLib.getInstance().isBlank(number)) {
113 | return phoneText;
114 | }
115 |
116 | number = number.replace("-", "");
117 |
118 | int length = number.length();
119 |
120 | if (number.length() >= 10) {
121 | phoneText = number.substring(0, 3) + "-"
122 | + number.substring(3, length-4) + "-"
123 | + number.substring(length-4, length);
124 | }
125 |
126 | return phoneText;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/lib/FileLib.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.lib;
2 |
3 | import android.content.Context;
4 | import android.os.Environment;
5 |
6 | import java.io.File;
7 |
8 | /**
9 | * 파일과 관련된 라이브러리
10 | */
11 | public class FileLib {
12 | public static final String TAG = FileLib.class.getSimpleName();
13 | private volatile static FileLib instance;
14 |
15 | public static FileLib getInstance() {
16 | if (instance == null) {
17 | synchronized (FileLib.class) {
18 | if (instance == null) {
19 | instance = new FileLib();
20 | }
21 | }
22 | }
23 | return instance;
24 | }
25 |
26 | /**
27 | * 파일을 저장할 수 있는 디렉토리 객체를 반환한다.
28 | * @param context 컨텍스트 객체
29 | * @return 파일 객체
30 | */
31 | private File getFileDir(Context context) {
32 | String state = Environment.getExternalStorageState();
33 | File filesDir;
34 |
35 | if (Environment.MEDIA_MOUNTED.equals(state)) {
36 | filesDir = context.getExternalFilesDir(null);
37 | } else {
38 | filesDir = context.getFilesDir();
39 | }
40 |
41 | return filesDir;
42 | }
43 |
44 | /**
45 | * 프로필 아이콘 파일을 저장할 파일 객체를 반환한다.
46 | * @param context 컨텍스트 객체
47 | * @param name 파일 이름
48 | * @return 파일 객체
49 | */
50 | public File getProfileIconFile(Context context, String name) {
51 | return new File(FileLib.getInstance().getFileDir(context), name + ".png");
52 | }
53 |
54 | /**
55 | * 이미지 파일 객체를 반환한다.
56 | * @param context 컨텍스트 객체
57 | * @param name 파일 이름
58 | * @return 파일 객체
59 | */
60 | public File getImageFile(Context context, String name) {
61 | return new File(FileLib.getInstance().getFileDir(context), name + ".png");
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/lib/GeoLib.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.lib;
2 |
3 | import android.Manifest;
4 | import android.content.Context;
5 | import android.content.pm.PackageManager;
6 | import android.location.Address;
7 | import android.location.Geocoder;
8 | import android.location.Location;
9 | import android.location.LocationManager;
10 | import android.support.v4.content.ContextCompat;
11 |
12 | import com.google.android.gms.maps.GoogleMap;
13 | import com.google.android.gms.maps.model.LatLng;
14 | import com.google.android.gms.maps.model.VisibleRegion;
15 | import com.mobitant.bestfood.item.GeoItem;
16 |
17 | import java.util.List;
18 | import java.util.Locale;
19 |
20 | /**
21 | * 위치 정보와 관련된 라이브러리
22 | */
23 | public class GeoLib {
24 | public final String TAG = GeoLib.class.getSimpleName();
25 | private volatile static GeoLib instance;
26 |
27 | public static GeoLib getInstance() {
28 | if (instance == null) {
29 | synchronized (GeoLib.class) {
30 | if (instance == null) {
31 | instance = new GeoLib();
32 | }
33 | }
34 | }
35 | return instance;
36 | }
37 |
38 | /**
39 | * 사용자의 현재 위도, 경도를 반환한다.
40 | * 실제로는 최근 측정된 위치 정보이다.
41 | * @param context 컨텍스트 객체
42 | */
43 | public void setLastKnownLocation(Context context) {
44 | LocationManager locationManager
45 | = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
46 | Location location = null;
47 |
48 | int result = ContextCompat.checkSelfPermission(context,
49 | Manifest.permission.ACCESS_FINE_LOCATION);
50 | if (result == PackageManager.PERMISSION_GRANTED) {
51 | location = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
52 | }
53 |
54 | if (location != null) {
55 | GeoItem.knownLatitude = location.getLatitude();
56 | GeoItem.knownLongitude = location.getLongitude();
57 | } else {
58 | //서울 설정
59 | GeoItem.knownLatitude = 37.566229;
60 | GeoItem.knownLongitude = 126.977689;
61 | }
62 | }
63 |
64 | /**
65 | * 지정된 위도경도 객체에 해당하는 주소 문자열을 반환한다.
66 | * @param context 컨텍스트 객체
67 | * @param latLng 위도, 경도 객체
68 | * @return Address 주소 객체
69 | */
70 | public Address getAddressString(Context context, LatLng latLng) {
71 | Geocoder geocoder = new Geocoder(context, Locale.getDefault());
72 |
73 | List list = null;
74 |
75 | try {
76 | list = geocoder.getFromLocation(latLng.latitude, latLng.longitude, 1);
77 | } catch (Exception e) {
78 | e.printStackTrace();
79 | }
80 |
81 | if (list != null && list.size() > 0) {
82 | return list.get(0);
83 | } else {
84 | return null;
85 | }
86 | }
87 |
88 | /**
89 | * Address 객체로부터 주소 문자열을 추출하여 반환한다.
90 | * @param address 주소 객체
91 | * @return 주소 문자열
92 | */
93 | public String getAddressString(Address address) {
94 | String address2 = "";
95 | if (address.getAddressLine(1) != null) {
96 | address2 = " " + address.getAddressLine(1);
97 | }
98 | return address.getAddressLine(0) + address2;
99 | }
100 |
101 | /**
102 | * 화면 중앙으로부터 화면 왼쪽까지의 거리를 반환한다.
103 | * @param map 구글 지도 객체
104 | * @return 거리(m)
105 | */
106 | public int getDistanceMeterFromScreenCenter(GoogleMap map) {
107 | VisibleRegion vr = map.getProjection().getVisibleRegion();
108 | double left = vr.latLngBounds.southwest.longitude;
109 |
110 | Location leftLocation = new Location("left");
111 | leftLocation.setLatitude(vr.latLngBounds.getCenter().latitude);
112 | leftLocation.setLongitude(left);
113 |
114 | Location center=new Location("center");
115 | center.setLatitude( vr.latLngBounds.getCenter().latitude);
116 | center.setLongitude( vr.latLngBounds.getCenter().longitude);
117 | return (int) center.distanceTo(leftLocation);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/lib/GoLib.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.lib;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.support.v4.app.Fragment;
6 | import android.support.v4.app.FragmentManager;
7 |
8 | import com.mobitant.bestfood.BestFoodInfoActivity;
9 | import com.mobitant.bestfood.BestFoodRegisterActivity;
10 | import com.mobitant.bestfood.ProfileActivity;
11 |
12 | /**
13 | * 액티비티나 프래그먼트 실행 라이브러리
14 | */
15 | public class GoLib {
16 | public final String TAG = GoLib.class.getSimpleName();
17 | private volatile static GoLib instance;
18 |
19 | public static GoLib getInstance() {
20 | if (instance == null) {
21 | synchronized (GoLib.class) {
22 | if (instance == null) {
23 | instance = new GoLib();
24 | }
25 | }
26 | }
27 | return instance;
28 | }
29 |
30 | /**
31 | * 프래그먼트를 보여준다.
32 | * @param fragmentManager 프래그먼트 매니저
33 | * @param containerViewId 프래그먼트를 보여줄 컨테이너 뷰 아이디
34 | * @param fragment 프래그먼트
35 | */
36 | public void goFragment(FragmentManager fragmentManager, int containerViewId,
37 | Fragment fragment) {
38 | fragmentManager.beginTransaction()
39 | .replace(containerViewId, fragment)
40 | .commit();
41 | }
42 |
43 | /**
44 | * 뒤로가기를 할 수 있는 프래그먼트를 보여준다.
45 | * @param fragmentManager 프래그먼트 매니저
46 | * @param containerViewId 프래그먼트를 보여줄 컨테이너 뷰 아이디
47 | * @param fragment 프래그먼트
48 | */
49 | public void goFragmentBack(FragmentManager fragmentManager, int containerViewId,
50 | Fragment fragment) {
51 | fragmentManager.beginTransaction()
52 | .replace(containerViewId, fragment)
53 | .addToBackStack(null)
54 | .commit();
55 | }
56 |
57 | /**
58 | * 이전 프래그먼트를 보여준다.
59 | * @param fragmentManager 프래그먼트 매니저
60 | */
61 | public void goBackFragment(FragmentManager fragmentManager) {
62 | fragmentManager.popBackStack();
63 | }
64 |
65 | /**
66 | * 프로파일 액티비티를 실행한다.
67 | * @param context 컨텍스트
68 | */
69 | public void goProfileActivity(Context context) {
70 | Intent intent = new Intent(context, ProfileActivity.class);
71 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
72 | context.startActivity(intent);
73 | }
74 |
75 | /**
76 | * 맛집 정보 등록 액티비티를 실행한다.
77 | * @param context 컨텍스트
78 | */
79 | public void goBestFoodRegisterActivity(Context context) {
80 | Intent intent = new Intent(context, BestFoodRegisterActivity.class);
81 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
82 | context.startActivity(intent);
83 | }
84 |
85 |
86 | /**
87 | * 맛집 정보 액티비티를 실행한다.
88 | * @param context 컨텍스트
89 | * @param infoSeq 맛집 정보 일련번호
90 | */
91 | public void goBestFoodInfoActivity(Context context, int infoSeq) {
92 | Intent intent = new Intent(context, BestFoodInfoActivity.class);
93 | intent.putExtra(BestFoodInfoActivity.INFO_SEQ, infoSeq);
94 | context.startActivity(intent);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/lib/KeepLib.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.lib;
2 |
3 | import android.os.Handler;
4 |
5 | import com.mobitant.bestfood.remote.RemoteService;
6 | import com.mobitant.bestfood.remote.ServiceGenerator;
7 |
8 | import retrofit2.Call;
9 | import retrofit2.Callback;
10 | import retrofit2.Response;
11 |
12 | /**
13 | * 즐겨찾기 관련 라이브러리
14 | */
15 | public class KeepLib {
16 | public final String TAG = KeepLib.class.getSimpleName();
17 | private volatile static KeepLib instance;
18 |
19 | public static KeepLib getInstance() {
20 | if (instance == null) {
21 | synchronized (KeepLib.class) {
22 | if (instance == null) {
23 | instance = new KeepLib();
24 | }
25 | }
26 | }
27 | return instance;
28 | }
29 |
30 | /**
31 | * 즐겨찾기 추가를 서버에 요청한다.
32 | * @param handler 결과를 응답할 핸들러
33 | * @param memberSeq 사용자 일련번호
34 | * @param infoSeq 맛집 정보 일련번호
35 | */
36 | public void insertKeep(final Handler handler, int memberSeq, final int infoSeq) {
37 | RemoteService remoteService = ServiceGenerator.createService(RemoteService.class);
38 |
39 | Call call = remoteService.insertKeep(memberSeq, infoSeq);
40 | call.enqueue(new Callback() {
41 | @Override
42 | public void onResponse(Call call, Response response) {
43 | if (response.isSuccessful()) {
44 | MyLog.d(TAG, "insertKeep " + response);
45 | handler.sendEmptyMessage(infoSeq);
46 | } else { // 등록 실패
47 | MyLog.d(TAG, "response error " + response.errorBody());
48 | }
49 | }
50 |
51 | @Override
52 | public void onFailure(Call call, Throwable t) {
53 | MyLog.d(TAG, "no internet connectivity");
54 | }
55 | });
56 | }
57 |
58 | /**
59 | * 즐겨찾기 삭제를 서버에 요청한다.
60 | * @param handler 결과를 응답할 핸들러
61 | * @param memberSeq 사용자 일련번호
62 | * @param infoSeq 맛집 정보 일련번호
63 | */
64 | public void deleteKeep(final Handler handler, int memberSeq, final int infoSeq) {
65 | RemoteService remoteService = ServiceGenerator.createService(RemoteService.class);
66 |
67 | Call call = remoteService.deleteKeep(memberSeq, infoSeq);
68 | call.enqueue(new Callback() {
69 | @Override
70 | public void onResponse(Call call, Response response) {
71 | if (response.isSuccessful()) {
72 | MyLog.d(TAG, "deleteKeep " + response);
73 | handler.sendEmptyMessage(infoSeq);
74 | } else { // 등록 실패
75 | MyLog.d(TAG, "response error " + response.errorBody());
76 | }
77 | }
78 |
79 | @Override
80 | public void onFailure(Call call, Throwable t) {
81 | MyLog.d(TAG, "no internet connectivity");
82 | }
83 | });
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/lib/MyLog.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.lib;
2 |
3 | import android.util.Log;
4 |
5 | import com.mobitant.bestfood.BuildConfig;
6 |
7 | /**
8 | * 로그 편의 클래스
9 | * 기존 로그를 좀 더 편하기 사용하기 위한 메소드로 구성
10 | * BuildConfig.DEBUG 값을 통해 개발 단계에서는 로그를 출력하고 마켓 런칭 단계에서는 로그 출력 안 함
11 | */
12 | public class MyLog {
13 | private static boolean enabled = BuildConfig.DEBUG;
14 |
15 | public static void d(String tag, String text) {
16 | if (!enabled) return;
17 |
18 | Log.d(tag, text);
19 | }
20 |
21 | public static void d(String text) {
22 | if (!enabled) return;
23 |
24 | Log.d("tag", text);
25 | }
26 |
27 | public static void d(String tag, Class> cls, String text) {
28 | if (!enabled) return;
29 |
30 | Log.d(tag, cls.getName() + "." + text);
31 | }
32 |
33 |
34 | public static void e(String tag, String text) {
35 | if (!enabled) return;
36 |
37 | Log.e(tag, text);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/lib/MyToast.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.lib;
2 |
3 | import android.content.Context;
4 | import android.widget.Toast;
5 |
6 | /**
7 | * 토스트 편의 클래스
8 | * 토스트를 좀 더 편하게 사용하기 위한 메소드로 구성
9 | */
10 | public class MyToast {
11 | public static void s(Context context, int id) {
12 | Toast.makeText(context, id, Toast.LENGTH_SHORT).show();
13 | }
14 |
15 | public static void s(Context context, String text) {
16 | Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
17 | }
18 |
19 | public static void l(Context context, int id) {
20 | Toast.makeText(context, id, Toast.LENGTH_LONG).show();
21 | }
22 |
23 | public static void l(Context context, String text) {
24 | Toast.makeText(context, text, Toast.LENGTH_LONG).show();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/lib/RemoteLib.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.lib;
2 |
3 | import android.content.Context;
4 | import android.net.ConnectivityManager;
5 | import android.net.NetworkInfo;
6 | import android.os.Handler;
7 |
8 | import com.mobitant.bestfood.remote.RemoteService;
9 | import com.mobitant.bestfood.remote.ServiceGenerator;
10 |
11 | import java.io.File;
12 |
13 | import okhttp3.MediaType;
14 | import okhttp3.MultipartBody;
15 | import okhttp3.RequestBody;
16 | import okhttp3.ResponseBody;
17 | import retrofit2.Call;
18 | import retrofit2.Callback;
19 | import retrofit2.Response;
20 |
21 | /**
22 | * 네트워크와 서버와 관련된 라이브러리
23 | */
24 | public class RemoteLib {
25 | public static final String TAG = RemoteLib.class.getSimpleName();
26 |
27 | private volatile static RemoteLib instance;
28 |
29 | public static RemoteLib getInstance() {
30 | if (instance == null) {
31 | synchronized (RemoteLib.class) {
32 | if (instance == null) {
33 | instance = new RemoteLib();
34 | }
35 | }
36 | }
37 | return instance;
38 | }
39 |
40 | /**
41 | * 네트워크 연결 여부를 반환한다.
42 | * @param context 컨텍스트
43 | * @return 네트워크 연결여부
44 | */
45 | public boolean isConnected(Context context) {
46 | try {
47 | ConnectivityManager cm =
48 | (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
49 | NetworkInfo info = cm.getActiveNetworkInfo();
50 |
51 | if (info != null) {
52 | return true;
53 | } else {
54 | return false;
55 | }
56 | } catch (Exception e) {
57 | return false;
58 | }
59 | }
60 |
61 | /**
62 | * 사용자 프로필 아이콘을 서버에 업로드한다.
63 | * @param memberSeq 사용자 일련번호
64 | * @param file 파일 객체
65 | */
66 | public void uploadMemberIcon(int memberSeq, File file) {
67 | RemoteService remoteService = ServiceGenerator.createService(RemoteService.class);
68 |
69 | RequestBody requestFile =
70 | RequestBody.create(MediaType.parse("multipart/form-data"), file);
71 |
72 | MultipartBody.Part body =
73 | MultipartBody.Part.createFormData("file", file.getName(), requestFile);
74 |
75 | RequestBody memberSeqBody =
76 | RequestBody.create(
77 | MediaType.parse("multipart/form-data"), "" + memberSeq);
78 |
79 | Call call =
80 | remoteService.uploadMemberIcon(memberSeqBody, body);
81 | call.enqueue(new Callback() {
82 | @Override
83 | public void onResponse(Call call,
84 | Response response) {
85 | MyLog.d(TAG, "uploadMemberIcon success");
86 | }
87 |
88 | @Override
89 | public void onFailure(Call call, Throwable t) {
90 | MyLog.e(TAG, "uploadMemberIcon fail");
91 | }
92 | });
93 | }
94 |
95 | /**
96 | * 맛집 이미지를 서버에 업로드한다.
97 | * @param infoSeq 맛집 정보 일련번호
98 | * @param imageMemo 이미지 설명
99 | * @param file 파일 객체
100 | * @param handler 처리 결과를 응답할 핸들러
101 | */
102 | public void uploadFoodImage(int infoSeq, String imageMemo, File file, final Handler handler) {
103 | RemoteService remoteService = ServiceGenerator.createService(RemoteService.class);
104 |
105 | RequestBody requestFile =
106 | RequestBody.create(MediaType.parse("multipart/form-data"), file);
107 |
108 | MultipartBody.Part body =
109 | MultipartBody.Part.createFormData("file", file.getName(), requestFile);
110 |
111 | RequestBody infoSeqBody =
112 | RequestBody.create(
113 | MediaType.parse("multipart/form-data"), "" + infoSeq);
114 | RequestBody imageMemoBody =
115 | RequestBody.create(
116 | MediaType.parse("multipart/form-data"), imageMemo);
117 |
118 | Call call =
119 | remoteService.uploadFoodImage(infoSeqBody, imageMemoBody, body);
120 | call.enqueue(new Callback() {
121 | @Override
122 | public void onResponse(Call call,
123 | Response response) {
124 | MyLog.d(TAG, "uploadFoodImage success");
125 | handler.sendEmptyMessage(0);
126 | }
127 |
128 | @Override
129 | public void onFailure(Call call, Throwable t) {
130 | MyLog.e(TAG, "uploadFoodImage fail");
131 | }
132 | });
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/lib/StringLib.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.lib;
2 |
3 | import android.content.Context;
4 |
5 | import com.mobitant.bestfood.R;
6 |
7 | /**
8 | * 문자열 관련 라이브러리
9 | */
10 | public class StringLib {
11 | public final String TAG = StringLib.class.getSimpleName();
12 | private volatile static StringLib instance;
13 |
14 | protected StringLib() {
15 | }
16 |
17 | public static StringLib getInstance() {
18 | if (instance == null) {
19 | synchronized (StringLib.class) {
20 | if (instance == null) {
21 | instance = new StringLib();
22 | }
23 | }
24 | }
25 | return instance;
26 | }
27 |
28 | /**
29 | * 문자열이 null이거나 ""인지를 파악한다.
30 | * @param str 문자열 객체
31 | * @return null이거나 ""이라면 true, 아니라면 false
32 | */
33 | public boolean isBlank(String str) {
34 | if (str == null || str.equals("")) {
35 | return true;
36 | } else {
37 | return false;
38 | }
39 | }
40 |
41 | /**
42 | * 문자열를 지정된 길이로 잘라서 반환한다.
43 | * @param context 컨텍스트
44 | * @param str 문자열 객체
45 | * @param max 최대 문자열 길이
46 | * @return 변경된 문자열 객체
47 | */
48 | public String getSubString(Context context, String str, int max) {
49 | if (str != null && str.length() > max) {
50 | return str.substring(0, max) + context.getResources().getString(R.string.skip_string);
51 | } else {
52 | return str;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/remote/RemoteService.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.remote;
2 |
3 | import com.mobitant.bestfood.item.FoodInfoItem;
4 | import com.mobitant.bestfood.item.KeepItem;
5 | import com.mobitant.bestfood.item.MemberInfoItem;
6 |
7 | import java.util.ArrayList;
8 |
9 | import okhttp3.MultipartBody;
10 | import okhttp3.RequestBody;
11 | import okhttp3.ResponseBody;
12 | import retrofit2.Call;
13 | import retrofit2.http.Body;
14 | import retrofit2.http.DELETE;
15 | import retrofit2.http.Field;
16 | import retrofit2.http.FormUrlEncoded;
17 | import retrofit2.http.GET;
18 | import retrofit2.http.Multipart;
19 | import retrofit2.http.POST;
20 | import retrofit2.http.Part;
21 | import retrofit2.http.Path;
22 | import retrofit2.http.Query;
23 |
24 | /**
25 | * 서버에 호출할 메소드를 선언하는 인터페이스
26 | */
27 | public interface RemoteService {
28 | String BASE_URL = "http://192.168.1.188:3000";
29 | String MEMBER_ICON_URL = BASE_URL + "/member/";
30 | String IMAGE_URL = BASE_URL + "/img/";
31 |
32 | //사용자 정보
33 | @GET("/member/{phone}")
34 | Call selectMemberInfo(@Path("phone") String phone);
35 |
36 | @POST("/member/info")
37 | Call insertMemberInfo(@Body MemberInfoItem memberInfoItem);
38 |
39 | @FormUrlEncoded
40 | @POST("/member/phone")
41 | Call insertMemberPhone(@Field("phone") String phone);
42 |
43 | @Multipart
44 | @POST("/member/icon_upload")
45 | Call uploadMemberIcon(@Part("member_seq") RequestBody memberSeq,
46 | @Part MultipartBody.Part file);
47 |
48 | //맛집 정보
49 | @GET("/food/info/{info_seq}")
50 | Call selectFoodInfo(@Path("info_seq") int foodInfoSeq,
51 | @Query("member_seq") int memberSeq);
52 |
53 | @POST("/food/info")
54 | Call insertFoodInfo(@Body FoodInfoItem infoItem);
55 |
56 | @Multipart
57 | @POST("/food/info/image")
58 | Call uploadFoodImage(@Part("info_seq") RequestBody infoSeq,
59 | @Part("image_memo") RequestBody imageMemo,
60 | @Part MultipartBody.Part file);
61 |
62 | @GET("/food/list")
63 | Call> listFoodInfo(@Query("member_seq") int memberSeq,
64 | @Query("user_latitude") double userLatitude,
65 | @Query("user_longitude") double userLongitude,
66 | @Query("order_type") String orderType,
67 | @Query("current_page") int currentPage);
68 |
69 |
70 | //지도
71 | @GET("/food/map/list")
72 | Call> listMap(@Query("member_seq") int memberSeq,
73 | @Query("latitude") double latitude,
74 | @Query("longitude") double longitude,
75 | @Query("distance") int distance,
76 | @Query("user_latitude") double userLatitude,
77 | @Query("user_longitude") double userLongitude);
78 |
79 |
80 | //즐겨찾기
81 | @POST("/keep/{member_seq}/{info_seq}")
82 | Call insertKeep(@Path("member_seq") int memberSeq, @Path("info_seq") int infoSeq);
83 |
84 | @DELETE("/keep/{member_seq}/{info_seq}")
85 | Call deleteKeep(@Path("member_seq") int memberSeq, @Path("info_seq") int infoSeq);
86 |
87 | @GET("/keep/list")
88 | Call> listKeep(@Query("member_seq") int memberSeq,
89 | @Query("user_latitude") double userLatitude,
90 | @Query("user_longitude") double userLongitude);
91 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobitant/bestfood/remote/ServiceGenerator.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood.remote;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.GsonBuilder;
5 | import com.mobitant.bestfood.BuildConfig;
6 |
7 | import okhttp3.OkHttpClient;
8 | import okhttp3.logging.HttpLoggingInterceptor;
9 | import retrofit2.Retrofit;
10 | import retrofit2.converter.gson.GsonConverterFactory;
11 |
12 | /**
13 | * 서버와 통신하기 위한 리트로핏을 사용하기 위한 클래스
14 | */
15 | public class ServiceGenerator {
16 | /**
17 | * 원격 호출을 정의한 인터페이스 메소드를 호출할 수 있는 서비스를 생성
18 | * @param serviceClass 원격 호출 메소드를 정의한 인터페이스
19 | * @return 인터페이스 구현체
20 | */
21 | public static S createService(Class serviceClass) {
22 | HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
23 | if (BuildConfig.DEBUG) {
24 | logging.setLevel(HttpLoggingInterceptor.Level.BODY);
25 | } else {
26 | logging.setLevel(HttpLoggingInterceptor.Level.NONE);
27 | }
28 |
29 | OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
30 | httpClient.addInterceptor(logging);
31 |
32 | Gson gson = new GsonBuilder()
33 | .setLenient()
34 | .create();
35 |
36 | Retrofit retorfit = new Retrofit.Builder()
37 | .baseUrl(RemoteService.BASE_URL)
38 | .addConverterFactory(GsonConverterFactory.create(gson))
39 | .client(httpClient.build())
40 | .build();
41 |
42 | return retorfit.create(serviceClass);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_basic_black_transparent.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_basic_gray.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_bestfood_drawer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/drawable/bg_bestfood_drawer.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_index.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/drawable/bg_index.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_round_gray.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_circle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 | -
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
10 |
11 |
12 | -
13 |
14 |
15 |
16 |
17 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_round_green.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
10 |
11 |
12 | -
13 |
14 |
15 |
16 |
17 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_round_red.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
10 |
11 |
12 | -
13 |
14 |
15 |
16 |
17 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/drawable/ic_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_keep_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/drawable/ic_keep_off.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_keep_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/drawable/ic_keep_on.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/drawable/ic_list.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_list2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/drawable/ic_list2.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/drawable/ic_map.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_person.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/drawable/ic_person.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/drawable/ic_profile.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_register.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/drawable/ic_register.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_tell.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/drawable/ic_tell.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_bestfood_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
13 |
14 |
21 |
22 |
30 |
31 |
35 |
36 |
41 |
42 |
43 |
44 |
51 |
52 |
57 |
58 |
68 |
69 |
74 |
75 |
76 |
77 |
88 |
89 |
90 |
91 |
98 |
99 |
107 |
108 |
117 |
118 |
119 |
120 |
127 |
128 |
133 |
134 |
143 |
144 |
153 |
154 |
155 |
156 |
164 |
165 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
180 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_bestfood_register.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_index.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
19 |
29 |
30 |
41 |
42 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_permission.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
20 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_profile.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
19 |
20 |
29 |
30 |
43 |
44 |
45 |
46 |
51 |
52 |
60 |
61 |
70 |
71 |
72 |
77 |
78 |
83 |
84 |
92 |
93 |
101 |
102 |
103 |
108 |
109 |
114 |
115 |
123 |
124 |
132 |
133 |
134 |
135 |
140 |
141 |
146 |
147 |
155 |
156 |
165 |
166 |
175 |
176 |
177 |
182 |
183 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_profile_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
21 |
22 |
29 |
30 |
37 |
38 |
41 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/app_bar_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_bestfood_keep.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_bestfood_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
25 |
26 |
36 |
37 |
47 |
48 |
54 |
55 |
56 |
57 |
63 |
64 |
77 |
78 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_bestfood_map.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
22 |
23 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_bestfood_register_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
23 |
24 |
37 |
38 |
39 |
40 |
53 |
54 |
60 |
61 |
70 |
71 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_bestfood_register_input.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
25 |
26 |
37 |
38 |
51 |
52 |
58 |
59 |
66 |
67 |
74 |
75 |
82 |
83 |
84 |
85 |
101 |
102 |
108 |
109 |
118 |
119 |
128 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_bestfood_register_location.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
26 |
27 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/loading_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/nav_header_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
19 |
20 |
30 |
31 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/row_bestfood_keep.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
23 |
24 |
31 |
32 |
38 |
39 |
49 |
50 |
55 |
56 |
57 |
58 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/row_bestfood_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
23 |
24 |
31 |
32 |
38 |
39 |
49 |
50 |
55 |
56 |
57 |
58 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/row_bestfood_map.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
21 |
22 |
28 |
29 |
30 |
31 |
38 |
39 |
45 |
46 |
56 |
57 |
65 |
66 |
67 |
68 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/activity_main_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_close.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_submit.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - 카메라
5 | - 앨범
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 | #000000
8 | #aa0000
9 | #2F9D27
10 | #880000
11 | #FFF
12 | #888888
13 |
14 | #88EEEEEE
15 | #FFFFFF
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 160dp
6 |
7 | 16dp
8 | 16dp
9 |
10 |
11 | 4dp
12 | 8dp
13 | 16dp
14 | 20dp
15 | 24dp
16 | 28dp
17 | 32dp
18 | 36dp
19 |
20 | 12sp
21 | 14sp
22 | 16sp
23 | 19sp
24 | 20sp
25 | 22sp
26 |
27 | 4dp
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | BestFood
4 |
5 | Open navigation drawer
6 | Close navigation drawer
7 |
8 | Settings
9 |
10 |
11 | 권한 확인중...
12 | 권한 설정
13 | 앱을 실행하기 위해서는 권한 설정을 해야 합니다!
14 | 권한을 설정하고 다시 실행해주세요!
15 |
16 |
17 | 최고의 음식점\n모두 모아\n베스트푸드
18 | Delicious
19 |
20 | 확인
21 | 네크워크에 연결되어 있지 않습니다.\n확인후 다시 실행해주세요.
22 |
23 |
24 | 맛집리스트
25 | 지도리스트
26 | 즐겨찾기
27 | 맛집등록
28 | 프로필설정
29 |
30 |
31 | ...
32 |
33 | 닫기
34 | 취소
35 | 설정
36 | 이전
37 | 다음
38 | 완료
39 |
40 |
41 | 프로필 설정
42 |
43 | 이름
44 | 성별
45 | 생일
46 | 전화
47 |
48 | 남자
49 | 여자
50 |
51 | 기기 번호
52 | 전화 번호
53 |
54 | 이름을 설정해주세요
55 | 변경사항을 저장하시겠습니까?
56 |
57 | 변경된 사항이 없습니다
58 | 데이터가 없습니다
59 |
60 | 사용자 등록에 실패하였습니다. 다시 시도해주세요.
61 |
62 |
63 | 앨범
64 | 카메라
65 |
66 |
67 | 맛집 등록
68 | 음식점이름을 입력해주세요
69 | 주소를 입력해주세요
70 | 전화번호를 입력해주세요
71 | 설명을 입력해주세요
72 | 이미지 설명을 입력해주세요(생략가능)
73 | 글자수 :
74 | /500
75 |
76 | 잘못된 번호입니다.
77 |
78 |
79 |
80 | 이미지 등록
81 | 이미지를 준비중입니다. 잠시 후에 다시 시도해주세요
82 | 이미지를 먼저 선택해주세요
83 |
84 |
85 | 거리순
86 | 인기순
87 | 최근순
88 |
89 |
90 | 목록보기
91 | 목록닫기
92 | 현재 위치에는 조회할 데이터가 없습니다.
93 | 지도를 좀 더 확대해야 배너가 보입니다.
94 | m
95 | Km
96 |
97 |
98 | 즐겨찾기 등록
99 | 즐겨찾기에 등록하시겠습니까?
100 |
101 | 즐겨찾기 삭제
102 | 즐겨찾기에서 삭제하시겠습니까?
103 |
104 |
105 | 상세설명
106 | 지도
107 | 위치보기
108 | 없음
109 | 잠시만 기다려주세요
110 | 잠시후에 다시 시도해주세요
111 |
112 |
113 | 즐겨찾기에 등록된 음식점이 없습니다.
114 |
115 |
116 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/test/java/com/mobitant/bestfood/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.mobitant.bestfood;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/web/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/web/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var path = require('path');
3 | var favicon = require('serve-favicon');
4 | var logger = require('morgan');
5 | var cookieParser = require('cookie-parser');
6 | var bodyParser = require('body-parser');
7 | var db = require('./db');
8 |
9 | var app = express();
10 |
11 | // view engine setup
12 | app.set('views', path.join(__dirname, 'views'));
13 | app.set('view engine', 'ejs');
14 |
15 | db.connect(function(err) {
16 | if (err) {
17 | console.log('Unable to connect to MySQL.')
18 | process.exit(1)
19 | }
20 | });
21 |
22 | // uncomment after placing your favicon in /public
23 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
24 | app.use(logger('dev'));
25 | app.use(bodyParser.json());
26 | app.use(bodyParser.urlencoded({ extended: false }));
27 | app.use(cookieParser());
28 | app.use(express.static(path.join(__dirname, 'public')));
29 |
30 | app.use('/member', require('./routes/member'));
31 | app.use('/food', require('./routes/food'));
32 | app.use('/keep', require('./routes/keep'));
33 |
34 | // catch 404 and forward to error handler
35 | app.use(function(req, res, next) {
36 | var err = new Error('Not Found');
37 | err.status = 404;
38 | next(err);
39 | });
40 |
41 | // error handler
42 | app.use(function(err, req, res, next) {
43 | // set locals, only providing error in development
44 | res.locals.message = err.message;
45 | res.locals.error = req.app.get('env') === 'development' ? err : {};
46 |
47 | // render the error page
48 | res.status(err.status || 500);
49 | res.render('error');
50 | });
51 |
52 | module.exports = app;
53 |
--------------------------------------------------------------------------------
/web/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | var app = require('../app');
8 | var debug = require('debug')('web:server');
9 | var http = require('http');
10 |
11 | /**
12 | * Get port from environment and store in Express.
13 | */
14 |
15 | var port = normalizePort(process.env.PORT || '3000');
16 | app.set('port', port);
17 |
18 | /**
19 | * Create HTTP server.
20 | */
21 |
22 | var server = http.createServer(app);
23 |
24 | /**
25 | * Listen on provided port, on all network interfaces.
26 | */
27 |
28 | server.listen(port);
29 | server.on('error', onError);
30 | server.on('listening', onListening);
31 |
32 | /**
33 | * Normalize a port into a number, string, or false.
34 | */
35 |
36 | function normalizePort(val) {
37 | var port = parseInt(val, 10);
38 |
39 | if (isNaN(port)) {
40 | // named pipe
41 | return val;
42 | }
43 |
44 | if (port >= 0) {
45 | // port number
46 | return port;
47 | }
48 |
49 | return false;
50 | }
51 |
52 | /**
53 | * Event listener for HTTP server "error" event.
54 | */
55 |
56 | function onError(error) {
57 | if (error.syscall !== 'listen') {
58 | throw error;
59 | }
60 |
61 | var bind = typeof port === 'string'
62 | ? 'Pipe ' + port
63 | : 'Port ' + port;
64 |
65 | // handle specific listen errors with friendly messages
66 | switch (error.code) {
67 | case 'EACCES':
68 | console.error(bind + ' requires elevated privileges');
69 | process.exit(1);
70 | break;
71 | case 'EADDRINUSE':
72 | console.error(bind + ' is already in use');
73 | process.exit(1);
74 | break;
75 | default:
76 | throw error;
77 | }
78 | }
79 |
80 | /**
81 | * Event listener for HTTP server "listening" event.
82 | */
83 |
84 | function onListening() {
85 | var addr = server.address();
86 | var bind = typeof addr === 'string'
87 | ? 'pipe ' + addr
88 | : 'port ' + addr.port;
89 | debug('Listening on ' + bind);
90 | }
91 |
--------------------------------------------------------------------------------
/web/db.js:
--------------------------------------------------------------------------------
1 | var mysql = require('mysql');
2 |
3 | var pool;
4 |
5 | exports.connect = function(done) {
6 | pool = mysql.createPool({
7 | connectionLimit: 100,
8 | host : 'localhost',
9 | user : 'root',
10 | password : 'bestfood',
11 | database : 'bestfood'
12 | });
13 | }
14 |
15 | exports.get = function() {
16 | return pool;
17 | }
--------------------------------------------------------------------------------
/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "node ./bin/www"
7 | },
8 | "dependencies": {
9 | "body-parser": "~1.17.1",
10 | "cookie-parser": "~1.4.3",
11 | "debug": "~2.6.3",
12 | "ejs": "~2.5.6",
13 | "express": "~4.15.2",
14 | "formidable": "^1.1.1",
15 | "morgan": "~1.8.1",
16 | "mysql": "^2.13.0",
17 | "serve-favicon": "~2.4.2"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/web/public/css/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 50px;
3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
4 | }
5 |
6 | a {
7 | color: #00B7FF;
8 | }
9 |
--------------------------------------------------------------------------------
/web/public/img/296.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/web/public/img/296.png
--------------------------------------------------------------------------------
/web/public/img/439.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/web/public/img/439.png
--------------------------------------------------------------------------------
/web/public/img/444.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/web/public/img/444.png
--------------------------------------------------------------------------------
/web/public/img/7_1457590813472.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairo96/and_node/fe5de1bb882029f9afaa3e8c51dbbbad3b2be523/web/public/img/7_1457590813472.png
--------------------------------------------------------------------------------
/web/routes/food.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var formidable = require('formidable');
3 | var db = require('../db')
4 | var router = express.Router();
5 |
6 | var LOADING_SIZE = 20;
7 | var DEFAULT_USER_LATITUDE = 37.566229;
8 | var DEFAULT_USER_LONGITUDE = 126.977689;
9 |
10 | //food/info
11 | router.post('/info', function(req, res, next) {
12 | if (!req.body.member_seq) {
13 | return res.sendStatus(400);
14 | }
15 |
16 | var member_seq = req.body.member_seq;
17 | var name = req.body.name;
18 | var tel = req.body.tel;
19 | var address = req.body.address;
20 | var latitude = req.body.latitude;
21 | var longitude = req.body.longitude;
22 | var description = req.body.description;
23 |
24 |
25 | var sql_insert =
26 | "insert into bestfood_info (member_seq, name, tel, address, latitude, longitude, description) " +
27 | "values(?, ?, ?, ?, ?, ?, ?); ";
28 |
29 | console.log(sql_insert);
30 |
31 | var params = [member_seq, name, tel, address, latitude, longitude, description];
32 |
33 | db.get().query(sql_insert, params, function (err, result) {
34 | console.log(result.insertId);
35 | res.status(200).send('' + result.insertId);
36 | });
37 | });
38 |
39 |
40 | //food/info/image
41 | router.post('/info/image', function (req, res) {
42 | var form = new formidable.IncomingForm();
43 |
44 | form.on('fileBegin', function (name, file){
45 | file.path = './public/img/' + file.name;
46 | });
47 |
48 | form.parse(req, function(err, fields, files) {
49 | var sql_insert = "insert into bestfood_info_image (info_seq, filename, image_memo) values (?, ?, ?);";
50 |
51 | db.get().query(sql_insert, [fields.info_seq, files.file.name, fields.image_memo], function (err, rows) {
52 | res.sendStatus(200);
53 | });
54 | });
55 | });
56 |
57 | //food/info/:seq
58 | router.get('/info/:seq', function(req, res, next) {
59 | var seq = req.params.seq;
60 | var member_seq = req.query.member_seq;
61 |
62 | var sql =
63 | "select a.*, " +
64 | " '0' as user_distance_meter, " +
65 | " if( exists(select * from bestfood_keep where member_seq = ? and a.seq = info_seq), 'true', 'false') as is_keep, " +
66 | " (select filename from bestfood_info_image where info_seq = a.seq order by seq limit 1) as image_filename " +
67 | "from bestfood_info as a " +
68 | "where seq = ? ; ";
69 | console.log("sql : " + sql);
70 |
71 | db.get().query(sql, [member_seq, seq], function (err, rows) {
72 | if (err) return res.sendStatus(400);;
73 |
74 | console.log("rows : " + JSON.stringify(rows));
75 | res.json(rows[0]);
76 | });
77 | });
78 |
79 | //food/list
80 | router.get('/list', function(req, res, next) {
81 | var member_seq = req.query.member_seq;
82 | var user_latitude = req.query.user_latitude || DEFAULT_USER_LATITUDE;
83 | var user_longitude = req.query.user_longitude || DEFAULT_USER_LONGITUDE;
84 | var order_type = req.query.order_type;
85 | var current_page = req.query.current_page || 0;
86 |
87 | if (!member_seq) {
88 | return res.sendStatus(400);
89 | }
90 |
91 | var order_add = '';
92 |
93 | if (order_type) {
94 | order_add = order_type + ' desc, user_distance_meter';
95 | } else {
96 | order_add = 'user_distance_meter';
97 | }
98 |
99 | var start_page = current_page * LOADING_SIZE;
100 |
101 |
102 | var sql =
103 | "select a.*, " +
104 | " (( 6371 * acos( cos( radians(?) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians(?) ) " +
105 | " + sin( radians(?) ) * sin( radians( latitude ) ) ) ) * 1000) AS user_distance_meter, " +
106 | " if( exists(select * from bestfood_keep where member_seq = ? and info_seq = a.seq), 'true', 'false') as is_keep, " +
107 | " (select filename from bestfood_info_image where info_seq = a.seq) as image_filename " +
108 | "from bestfood_info as a " +
109 | "order by " + order_add + " " +
110 | "limit ? , ? ; ";
111 | console.log("sql : " + sql);
112 | console.log("order_add : " + order_add);
113 |
114 | var params = [user_latitude, user_longitude, user_latitude, member_seq, start_page, LOADING_SIZE];
115 |
116 | db.get().query(sql, params, function (err, rows) {
117 | if (err) return res.sendStatus(400);
118 |
119 | console.log("rows : " + JSON.stringify(rows));
120 | res.status(200).json(rows);
121 | });
122 | });
123 |
124 | //food/map/list
125 | router.get('/map/list', function(req, res, next) {
126 | var member_seq = req.query.member_seq;
127 | var latitude = req.query.latitude;
128 | var longitude = req.query.longitude;
129 | var distance = req.query.distance;
130 | var user_latitude = req.query.user_latitude || DEFAULT_USER_LATITUDE;
131 | var user_longitude = req.query.user_longitude || DEFAULT_USER_LONGITUDE;
132 |
133 | if (!member_seq || !latitude || !longitude) {
134 | return res.sendStatus(400);
135 | }
136 |
137 | var sql =
138 | "select a.*, " +
139 | " (( 6371 * acos( cos( radians(?) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians(?) ) " +
140 | " + sin( radians(?) ) * sin( radians( latitude ) ) ) ) * 1000) AS distance_meter," +
141 | " (( 6371 * acos( cos( radians(?) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians(?) ) " +
142 | " + sin( radians(?) ) * sin( radians( latitude ) ) ) ) * 1000) AS user_distance_meter," +
143 | " IF(EXISTS (select * from bestfood_keep where member_seq = ? and a.seq = info_seq), 'true', 'false') as is_keep," +
144 | " (select filename from bestfood_info_image where info_seq = a.seq) as image_filename " +
145 | "from bestfood_info as a " +
146 | "having distance_meter <= ? " +
147 | "order by user_distance_meter ";
148 | console.log("sql : " + sql);
149 |
150 | var params = [latitude, longitude, latitude, user_latitude, user_longitude, user_latitude, member_seq, distance];
151 |
152 | db.get().query(sql, params, function (err, rows) {
153 | if (err) return res.sendStatus(400);
154 |
155 | console.log("rows : " + JSON.stringify(rows));
156 | res.status(200).json(rows);
157 | });
158 | });
159 |
160 | module.exports = router;
--------------------------------------------------------------------------------
/web/routes/keep.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var db = require('../db')
3 | var router = express.Router();
4 |
5 | //keep/list
6 | router.get('/list', function(req, res, next) {
7 | var member_seq = req.query.member_seq;
8 | var user_latitude = req.query.user_latitude;
9 | var user_longitude = req.query.user_longitude;
10 |
11 | console.log(member_seq);
12 |
13 | if (!member_seq) {
14 | return res.sendStatus(400);
15 | }
16 |
17 | var sql =
18 | "select a.seq as keep_seq, a.member_seq as keep_member_seq, a.reg_date as keep_date, " +
19 | " b.*, " +
20 | " (( 6371 * acos( cos( radians(?) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians(?) ) " +
21 | " + sin( radians(?) ) * sin( radians( latitude ) ) ) ) * 1000) AS user_distance_meter, " +
22 | " 'true' as is_keep, " +
23 | " (select filename from bestfood_info_image where info_seq = a.info_seq) as image_filename " +
24 | "from bestfood_keep as a left join bestfood_info as b " +
25 | " on (a.info_seq = b.seq) " +
26 | "where a.member_seq = ? " +
27 | "order by a.reg_date desc ";
28 | console.log("sql : " + sql);
29 |
30 | db.get().query(sql, [user_latitude, user_longitude, user_latitude, member_seq], function (err, rows) {
31 | if (err) return res.sendStatus(400);
32 | res.status(200).json(rows);
33 | });
34 | });
35 |
36 | //keep/:member_seq/:info_seq
37 | router.post('/:member_seq/:info_seq', function(req, res, next) {
38 | var member_seq = req.params.member_seq;
39 | var info_seq = req.params.info_seq;
40 |
41 | console.log(member_seq);
42 | console.log(info_seq);
43 |
44 | if (!member_seq || !info_seq) {
45 | return res.sendStatus(400);
46 | }
47 |
48 | var sql_select = "select count(*) as cnt from bestfood_keep where member_seq = ? and info_seq = ?;";
49 | var sql_insert = "insert into bestfood_keep (member_seq, info_seq) values(?, ?);";
50 | var sql_update = "update bestfood_info set keep_cnt = keep_cnt+1 where seq = ? ";
51 |
52 | db.get().query(sql_select, [member_seq, info_seq], function (err, rows) {
53 | if (rows[0].cnt > 0) {
54 | return res.sendStatus(400);
55 | }
56 |
57 | db.get().query(sql_insert, [member_seq, info_seq], function (err, rows) {
58 | db.get().query(sql_update, info_seq, function (err, rows) {
59 | if (err) return res.sendStatus(400);
60 | res.sendStatus(200);
61 | });
62 | });
63 | });
64 | });
65 |
66 | //keep/:member_seq/:info_seq
67 | router.delete('/:member_seq/:info_seq', function(req, res, next) {
68 | var member_seq = req.params.member_seq;
69 | var info_seq = req.params.info_seq;
70 |
71 | console.log(member_seq);
72 | console.log(info_seq);
73 |
74 | if (!member_seq || !info_seq) {
75 | return res.sendStatus(400);
76 | }
77 |
78 | var sql_delete = "delete from bestfood_keep where member_seq = ? and info_seq = ? ";
79 | var sql_update = "update bestfood_info set keep_cnt = keep_cnt-1 where seq = ? ";
80 |
81 | db.get().query(sql_delete, [member_seq, info_seq], function (err, rows) {
82 | db.get().query(sql_update, info_seq, function (err, rows) {
83 | if (err) return res.sendStatus(400);
84 | res.sendStatus(200);
85 | });
86 | });
87 | });
88 |
89 | module.exports = router;
--------------------------------------------------------------------------------
/web/routes/member.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var formidable = require('formidable');
3 | var db = require('../db')
4 | var router = express.Router();
5 |
6 | //member/:phone
7 | router.get('/:phone', function(req, res, next) {
8 | var phone = req.params.phone;
9 |
10 | var sql = "select * " +
11 | "from bestfood_member " +
12 | "where phone = ? limit 1;";
13 | console.log("sql : " + sql);
14 |
15 | db.get().query(sql, phone, function (err, rows) {
16 | console.log("rows : " + JSON.stringify(rows));
17 | console.log("row.length : " + rows.length);
18 | if (rows.length > 0) {
19 | res.status(200).json(rows[0]);
20 | } else {
21 | res.sendStatus(400);
22 | }
23 | });
24 | });
25 |
26 | //member/phone
27 | router.post('/phone', function(req, res) {
28 | var phone = req.body.phone;
29 |
30 | var sql_count = "select count(*) as cnt " +
31 | "from bestfood_member " +
32 | "where phone = ?;";
33 | console.log("sql_count : " + sql_count);
34 |
35 | var sql_insert = "insert into bestfood_member (phone) values(?);";
36 |
37 | db.get().query(sql_count, phone, function (err, rows) {
38 | console.log(rows);
39 | console.log(rows[0].cnt);
40 |
41 | if (rows[0].cnt > 0) {
42 | return res.sendStatus(400);
43 | }
44 |
45 | db.get().query(sql_insert, phone, function (err, result) {
46 | console.log(err);
47 | if (err) return res.sendStatus(400);
48 | res.status(200).send('' + result.insertId);
49 | });
50 | });
51 | });
52 |
53 | //member/info
54 | router.post('/info', function(req, res) {
55 | var phone = req.body.phone;
56 | var name = req.body.name;
57 | var sextype = req.body.sextype;
58 | var birthday = req.body.birthday;
59 |
60 | console.log({name, sextype, birthday, phone});
61 |
62 | var sql_count = "select count(*) as cnt " +
63 | "from bestfood_member " +
64 | "where phone = ?;";
65 |
66 | var sql_insert = "insert into bestfood_member (phone, name, sextype, birthday) values(?, ?, ?, ?);";
67 | var sql_update = "update bestfood_member set name = ?, sextype = ?, birthday = ? where phone = ?; ";
68 | var sql_select = "select seq from bestfood_member where phone = ?; ";
69 |
70 | db.get().query(sql_count, phone, function (err, rows) {
71 | if (rows[0].cnt > 0) {
72 | console.log("sql_update : " + sql_update);
73 |
74 | db.get().query(sql_update, [name, sextype, birthday, phone], function (err, result) {
75 | if (err) return res.sendStatus(400);
76 | console.log(result);
77 |
78 | db.get().query(sql_select, phone, function (err, rows) {
79 | if (err) return res.sendStatus(400);
80 |
81 | res.status(200).send('' + rows[0].seq);
82 | });
83 | });
84 | } else {
85 | console.log("sql_insert : " + sql_insert);
86 |
87 | db.get().query(sql_insert, [phone, name, sextype, birthday], function (err, result) {
88 | if (err) return res.sendStatus(400);
89 |
90 | res.status(200).send('' + result.insertId);
91 | });
92 | }
93 | });
94 | });
95 |
96 | //member/icon_upload
97 | router.post('/icon_upload', function (req, res) {
98 | var form = new formidable.IncomingForm();
99 |
100 | form.on('fileBegin', function (name, file){
101 | file.path = './public/member/' + file.name;
102 | });
103 |
104 | form.parse(req, function(err, fields, files) {
105 | var sql_update = "update bestfood_member set member_icon_filename = ? where seq = ?;";
106 |
107 | db.get().query(sql_update, [files.file.name, fields.member_seq], function (err, rows) {
108 | res.sendStatus(200);
109 | });
110 | });
111 | });
112 |
113 |
114 | module.exports = router;
--------------------------------------------------------------------------------
/web/views/error.ejs:
--------------------------------------------------------------------------------
1 | <%= message %>
2 | <%= error.status %>
3 | <%= error.stack %>
4 |
--------------------------------------------------------------------------------
/web/views/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= title %>
5 |
6 |
7 |
8 | <%= title %>
9 | Welcome to <%= title %>
10 |
11 |
12 |
--------------------------------------------------------------------------------