-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
부산대 Android_김현민 2주차 과제 수정 #72
Open
khyeonm
wants to merge
16
commits into
kakao-tech-campus-2nd-step2:khyeonm
Choose a base branch
from
khyeonm:step2-1
base: khyeonm
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 15 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
6b8ac02
Docs: Update README.md
khyeonm cacee4b
Feat: Complete search screen layout
khyeonm cb73f1e
Feat: Add data to the database
khyeonm 6bd8cf7
Docs: Update README.md
khyeonm 0fc0da3
Feat: Create item_view
khyeonm f94b9b8
Feat: Modify main view
khyeonm a400cbd
Feat: Implement search list
khyeonm 60e50ab
Feat: Complete saved element view
khyeonm 36ad032
Feat: Complete save action
khyeonm 0644db3
Feat: Add persistent save function
khyeonm 8dcc8a7
Design: Modify save element design
khyeonm 3959c94
Update README.md
khyeonm c03af09
Merge branch 'khyeonm' into step2
khyeonm f872bc9
Fix: Resolve error
khyeonm e0d44b6
Docs: Update README.md
khyeonm 816bf02
Merge branch 'khyeonm' into step2-1
LeeOhHyung File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,28 @@ | ||
# 맵 위치 검색 안드로이드 앱 | ||
- 카테캠 Step2 Week2 | ||
- 연락처 추가 및 연락처 조회가 가능한 연락처 목록을 통해 연락처를 관리하는 안드로이드 앱 | ||
- 장소 검색 및 장소 저장이 가능한 안드로이드 앱 | ||
|
||
## 기능 | ||
**1. 검색 화면** | ||
- 검색어 입력 칸 + 검색어 전체 삭제 버튼 | ||
- 검색 결과 표시 레이아웃 | ||
- 검색어 x눌러 삭제 가능 | ||
|
||
**2. 로컬 데이터베이스 생성** | ||
|
||
**3. 검색 결과 목록** | ||
- RecyclerView 사용 | ||
- 선택하면 검색어 저장 목록에 추가됨 | ||
- 검색창과 검색결과 목록 사이 | ||
|
||
**4. 검색어 저장 목록** | ||
- 가로 스크롤 | ||
- x눌러서 삭제 가능 | ||
- 앱을 재실행하여도 유지 | ||
- 이미 저장된 목록 선택 시 가장 오른쪽에 위치 | ||
|
||
## 실행 | ||
- MainActivity.kt 에서 시작 | ||
|
||
## 환경 | ||
- Kotlin | ||
- Android Studio | ||
- Android Studio |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package campus.tech.kakao.map | ||
|
||
import android.view.LayoutInflater | ||
import android.view.View | ||
import android.view.ViewGroup | ||
import android.widget.TextView | ||
import androidx.recyclerview.widget.RecyclerView | ||
|
||
class Adapter(private var profiles: List<Profile>) : RecyclerView.Adapter<Adapter.ProfileViewHolder>() { | ||
|
||
interface OnItemClickListener { | ||
fun onItemClick(name: String) | ||
} | ||
|
||
var listener: OnItemClickListener? = null | ||
fun setOnItemClickListener(listener: OnItemClickListener) { | ||
this.listener = listener | ||
} | ||
|
||
inner class ProfileViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { | ||
val tvName: TextView = itemView.findViewById(R.id.tvName) | ||
val tvAddress: TextView = itemView.findViewById(R.id.tvAddress) | ||
val tvType: TextView = itemView.findViewById(R.id.tvType) | ||
|
||
init { | ||
itemView.setOnClickListener { | ||
bindingAdapterPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { position -> | ||
listener?.onItemClick(profiles[position].name) | ||
} | ||
} | ||
} | ||
} | ||
|
||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder { | ||
val view = LayoutInflater.from(parent.context).inflate(R.layout.activity_item_view, parent, false) | ||
return ProfileViewHolder(view) | ||
} | ||
|
||
override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) { | ||
val profile = profiles[position] | ||
holder.tvName.text = profile.name | ||
holder.tvAddress.text = profile.address | ||
holder.tvType.text = profile.type | ||
} | ||
|
||
override fun getItemCount(): Int { | ||
return profiles.size | ||
} | ||
|
||
fun updateProfiles(newProfiles: List<Profile>) { | ||
profiles = newProfiles | ||
notifyDataSetChanged() | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,61 +1,76 @@ | ||
package campus.tech.kakao.map | ||
|
||
import android.content.ContentValues | ||
import android.content.Context | ||
import android.database.Cursor | ||
import android.database.sqlite.SQLiteDatabase | ||
import android.database.sqlite.SQLiteOpenHelper | ||
import android.os.Parcel | ||
import android.os.Parcelable | ||
|
||
private const val TABLE_PLACE = "places" | ||
private const val COLUMN_TYPE = "type" // | ||
private const val COLUMN_TYPE = "type" | ||
private const val COLUMN_NAME = "name" | ||
private const val COLUMN_ADDRESS = "address" | ||
|
||
class DBHelper(context: Context): SQLiteOpenHelper(context, "place.db", null, 1) { | ||
class DBHelper(context: Context) : SQLiteOpenHelper(context, "place.db", null, 1) { | ||
override fun onCreate(db: SQLiteDatabase?) { | ||
db?.execSQL( | ||
"CREATE TABLE $TABLE_PLACE (" + | ||
"$COLUMN_TYPE varchar(30) NOT NULL," + | ||
"$COLUMN_NAME varchar(30) NOT NULL," + | ||
"$COLUMN_ADDRESS varchar(30) NOT NULL" + | ||
"$COLUMN_TYPE VARCHAR(30) NOT NULL," + | ||
"$COLUMN_NAME VARCHAR(30) NOT NULL," + | ||
"$COLUMN_ADDRESS VARCHAR(30) NOT NULL" + | ||
");" | ||
) | ||
|
||
insertPharData() | ||
insertCafeData() | ||
insertPharData(db) | ||
insertCafeData(db) | ||
} | ||
|
||
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { | ||
db?.execSQL("DROP TABLE IF EXISTS $TABLE_PLACE") | ||
onCreate(db) | ||
} | ||
|
||
fun insertPharData() { | ||
val db = this.writableDatabase | ||
private fun insertPharData(db: SQLiteDatabase?) { | ||
val type = "약국" | ||
val name = "약국" | ||
val address = "서울 강남구 대치동" | ||
|
||
for (i in 1..30) { | ||
val name = "$name$i" | ||
val address = "$address $i" | ||
db.execSQL("INSERT INTO $TABLE_PLACE ($COLUMN_TYPE, $COLUMN_NAME, $COLUMN_ADDRESS) VALUES ('$type', '$name', '$address');") | ||
val nameWithIndex = "$name$i" | ||
val addressWithIndex = "$address $i" | ||
db?.execSQL("INSERT INTO $TABLE_PLACE ($COLUMN_TYPE, $COLUMN_NAME, $COLUMN_ADDRESS) VALUES ('$type', '$nameWithIndex', '$addressWithIndex');") | ||
} | ||
} | ||
|
||
fun insertCafeData() { | ||
val db = this.writableDatabase | ||
private fun insertCafeData(db: SQLiteDatabase?) { | ||
val type = "카페" | ||
val name = "카페" | ||
val address = "서울 성동구 성수동" | ||
|
||
for (i in 1..30) { | ||
val name = "$name$i" | ||
val address = "$address $i" | ||
db.execSQL("INSERT INTO $TABLE_PLACE ($COLUMN_TYPE, $COLUMN_NAME, $COLUMN_ADDRESS) VALUES ('$type', '$name', '$address');") | ||
val nameWithIndex = "$name$i" | ||
val addressWithIndex = "$address $i" | ||
db?.execSQL("INSERT INTO $TABLE_PLACE ($COLUMN_TYPE, $COLUMN_NAME, $COLUMN_ADDRESS) VALUES ('$type', '$nameWithIndex', '$addressWithIndex');") | ||
} | ||
} | ||
|
||
fun searchProfiles(query: String): List<Profile> { | ||
val profiles = mutableListOf<Profile>() | ||
val db = this.readableDatabase | ||
val cursor: Cursor = db.rawQuery( | ||
"SELECT * FROM $TABLE_PLACE WHERE $COLUMN_NAME LIKE ? OR $COLUMN_ADDRESS LIKE ? OR $COLUMN_TYPE LIKE ?", | ||
arrayOf("%$query%", "%$query%", "%$query%") | ||
) | ||
|
||
if (cursor.moveToFirst()) { | ||
do { | ||
val type = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_TYPE)) | ||
val name = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME)) | ||
val address = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_ADDRESS)) | ||
profiles.add(Profile(name, address, type)) | ||
} while (cursor.moveToNext()) | ||
} | ||
cursor.close() | ||
return profiles | ||
} | ||
} | ||
|
151 changes: 148 additions & 3 deletions
151
app/src/main/java/campus/tech/kakao/map/MainActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,160 @@ | ||
package campus.tech.kakao.map | ||
|
||
import android.database.Cursor | ||
import android.content.Context | ||
import android.os.Bundle | ||
import android.text.Editable | ||
import android.text.TextWatcher | ||
import android.view.LayoutInflater | ||
import android.view.View | ||
import android.widget.Button | ||
import android.widget.EditText | ||
import android.widget.HorizontalScrollView | ||
import android.widget.ImageView | ||
import android.widget.TextView | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.appcompat.widget.LinearLayoutCompat | ||
import androidx.constraintlayout.widget.ConstraintLayout | ||
import androidx.recyclerview.widget.LinearLayoutManager | ||
import androidx.recyclerview.widget.RecyclerView | ||
import org.json.JSONArray | ||
|
||
class MainActivity : AppCompatActivity() { | ||
lateinit var adapter: Adapter | ||
lateinit var dbHelper: DBHelper | ||
lateinit var tvNoResult: TextView | ||
lateinit var llSave: LinearLayoutCompat | ||
lateinit var hScrollView: HorizontalScrollView | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContentView(R.layout.activity_main) | ||
|
||
val dbHelper = DBHelper(this) | ||
val db = dbHelper.writableDatabase | ||
dbHelper = DBHelper(this) | ||
|
||
val etSearch = findViewById<EditText>(R.id.etSearch) | ||
tvNoResult = findViewById(R.id.tvNoResult) | ||
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) | ||
recyclerView.layoutManager = LinearLayoutManager(this) | ||
val btnClose = findViewById<Button>(R.id.btnClose) | ||
llSave = findViewById(R.id.llSave) | ||
hScrollView = findViewById(R.id.hScrollView) | ||
|
||
adapter = Adapter(emptyList()) | ||
recyclerView.adapter = adapter | ||
|
||
tvNoResult.visibility = TextView.VISIBLE | ||
|
||
etSearch.addTextChangedListener(object : TextWatcher { | ||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} | ||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} | ||
override fun afterTextChanged(s: Editable?) { | ||
val search = s.toString() | ||
searchProfiles(search) | ||
} | ||
}) | ||
|
||
adapter.setOnItemClickListener(object : Adapter.OnItemClickListener { | ||
override fun onItemClick(name: String) { | ||
if (isProfileInSearchSave(name)) { | ||
removeSavedItem(name) | ||
} | ||
addSavedItem(name) | ||
} | ||
}) | ||
|
||
btnClose.setOnClickListener { | ||
etSearch.text.clear() | ||
} | ||
loadSavedItems() | ||
} | ||
|
||
override fun onPause() { | ||
super.onPause() | ||
saveSavedItems() | ||
} | ||
|
||
fun saveSavedItems() { | ||
val sharedPreferences = getSharedPreferences("SavedItems", MODE_PRIVATE) | ||
val editor = sharedPreferences.edit() | ||
val savedNames = JSONArray() | ||
for (i in 0 until llSave.childCount) { | ||
val savedView = llSave.getChildAt(i) as? ConstraintLayout | ||
val tvSaveName = savedView?.findViewById<TextView>(R.id.tvSaveName) | ||
if (tvSaveName != null) { | ||
savedNames.put(tvSaveName.text.toString()) | ||
} | ||
} | ||
editor.putString("savedNames", savedNames.toString()) | ||
editor.apply() | ||
} | ||
|
||
fun loadSavedItems() { | ||
val sharedPreferences = getSharedPreferences("SavedItems", MODE_PRIVATE) | ||
val savedNamesString = sharedPreferences.getString("savedNames", "[]") | ||
val savedNames = JSONArray(savedNamesString) | ||
for (i in 0 until savedNames.length()) { | ||
val name = savedNames.getString(i) | ||
addSavedItem(name) | ||
} | ||
} | ||
|
||
fun searchProfiles(query: String) { | ||
if (query.isEmpty()) { | ||
tvNoResult.visibility = TextView.VISIBLE | ||
adapter.updateProfiles(emptyList()) | ||
} else { | ||
val profileList = dbHelper.searchProfiles(query) | ||
if (profileList.isEmpty()) { | ||
tvNoResult.visibility = TextView.VISIBLE | ||
} else { | ||
tvNoResult.visibility = TextView.GONE | ||
} | ||
adapter.updateProfiles(profileList) | ||
} | ||
} | ||
|
||
fun addSavedItem(name: String) { | ||
val savedView = LayoutInflater.from(this) | ||
.inflate(R.layout.search_save, llSave, false) as ConstraintLayout | ||
|
||
val tvSaveName = savedView.findViewById<TextView>(R.id.tvSaveName) | ||
val ivDelete = savedView.findViewById<ImageView>(R.id.ivDelete) | ||
|
||
tvSaveName.text = name | ||
ivDelete.setOnClickListener { | ||
llSave.removeView(savedView) | ||
} | ||
|
||
llSave.addView(savedView) | ||
hScrollView.visibility = View.VISIBLE | ||
scrollToEndOfSearchSave() | ||
} | ||
|
||
fun removeSavedItem(name: String) { | ||
for (i in 0 until llSave.childCount) { | ||
val savedView = llSave.getChildAt(i) as? ConstraintLayout | ||
val tvSaveName = savedView?.findViewById<TextView>(R.id.tvSaveName) | ||
if (tvSaveName?.text.toString() == name) { | ||
llSave.removeViewAt(i) | ||
break | ||
} | ||
} | ||
} | ||
|
||
fun isProfileInSearchSave(name: String): Boolean { | ||
for (i in 0 until llSave.childCount) { | ||
val savedView = llSave.getChildAt(i) as? ConstraintLayout | ||
val tvSaveName = savedView?.findViewById<TextView>(R.id.tvSaveName) | ||
if (tvSaveName?.text.toString() == name) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
fun scrollToEndOfSearchSave() { | ||
hScrollView.post { | ||
hScrollView.fullScroll(View.FOCUS_RIGHT) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package campus.tech.kakao.map | ||
|
||
data class Profile ( | ||
val name: String, | ||
val address: String, | ||
val type: String | ||
) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
db가 null 인지 체크를 미리해서, early return 시키거나 null일때는 이 메소드가 호출안되도록 하는게 좋을것 같습니다. 왜냐면, db가 null 일때도 loop를 30회 수행하고 있을것이라 무의미한 연산이 수행될것 같네요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아래
insertCafeData
메소드도 동일합니다.