Skip to content

Commit

Permalink
feat: initialize base code
Browse files Browse the repository at this point in the history
author woowahan-pjs <[email protected]> 1683522324 +0900
committer hyemdooly <[email protected]> 1684889735 +0900

feat: set up the project

[둘리] 1, 2단계 쇼핑 장바구니 제출합니다. (woowacourse#20)

* docs: 기능 목록 작성

* feat: Domain 모듈 생성

* feat: 상훔 목록을 관리하는 Repository Interface 생성

* feat: 장바구니를 관리하는 CartRepository Interface 생성

* feat: 최근 본 상품 목록을 관리하는 Repository Interface 생성

* feat: 상품 정보를 담는 Product, 가격을 담는 Price 생성

* feat: 상품의 정보를 리턴하는 ProductMockRepository 구현

* feat: ProductListActivity 이미지 제외한 화면 구성 완료

* feat: 메인 색깔 변경

* feat: 액션 바에 장바구니 바로가기 메뉴 추가

* feat: 장바구니 화면의 액션바 안의 백버튼을 누르면 뒤로 이동하는 기능 추가

* feat: 상품 이미지 Glide 적용

* refactor: 패키지 이동

* refactor: ProductListActivity MVP 패턴 적용

* refactor: 장바구니 아이템 구현

* feat: 장바구니 리스트 뷰 구현

* feat: 상품 상세 뷰 구현

* feat: 장바구니 DataBase 생성 로직 구현

* feat: CartDbRepository 구현

* feat: ProductList OnClick 추가, DB 적용

* feat: 상품 상세 페이지 액션 바에 뒤로가기 버튼 추가

* refactor: MVP 패턴으로 변경

* feat: 최근 본 상품 리스트 추가

* feat: 최근 본 상품 datebase 기능 추가

* feat: CartProduct DB 적용, 삭제 기능 추가

* refactor: find 리턴값 수정

* refactor: notifyItemRemoved 적용

* feat: 페이지네이션 구현 중

* feat: 더보기 구현중

* feat: 더보기 구현 완료

* feat: 카트 페이지네이션 구현 중

* test: ProductListPresenterTest 작성

* refactor: ProductDetailPresenter cartRepository 추상화 적용

* test: ProductDetailPresenter 테스트 추가

* refactor: Pagination 적용중

* refactor: Pagination 적용중

* feat: Pagination Button onClick 수정

* fix: 페이지가 넘어가지 않는 오류 수정

* refactor: onCreate 코드 함수 분리

* refactor: 너무 긴 코드 개행

* refactor: 상수 선언

* refactor: constraintLayout 자식 뷰 match_parent -> 0dp로 수정

* refactor: xml formatting

* refactor: asSequence 사용하여 map 호출 시간 단축

* refactor: Next, Prev Pagination 분리

* refactor: CartPageStatus 생성, CartAdapter 리팩터링

* refactor: LayoutManager xml로 이동

* refactor: 상수 선언

* refactor: CartAdapter에서 CartViewItem 사용하여 Items를 하나로 묶음

* refactor: ProductListActivity 리사이클러뷰 리팩터링

* test: package 이동, ProductListPresenterTest 수정, CartPresenterTest 작성

* refactor: 테스트 코드에서 whildcard import 수정

---------

Co-authored-by: hyunji1203 <[email protected]>

refactor: 함수 간소화

refactor: supportActionBar Label 설정 삭제, manifest 이용

refactor: test 코드 수정

feat: ProductList 상품 장바구니 추가 뷰 수정

docs: 3단계 요구사항 정리

feat: 장바구니 버튼 생성

feat: ProductDetail 수량 선택 Dialog 구현

feat: CartActivity 하단 뷰 구현

feat: CartActivity Item layout 변경사항 수정

feat: cart badge 생성

feat: ProductListActivity List Count 구현

feat: ProductListActivity AppBar Cart Badge 구현

feat: ProductDetailActivity 마지막으로 본 상품 startActivity

feat: ProductDetailActivity 다른 액티비티 실행 시 Stack 조절

feat: Cart 아이콘 장바구니 비었으면 Gone으로 수정

feat: ProductListPresenter에서 삭제했던 최근 본 상품 복구

refactor: domain 코드 이동, ktlintformat

feat: CartActivity 상품 개수 업데이트 구현

feat: CartActivity, CartSystem 선택 구현 (리팩토링 필수)

feat: ProductListActivity <-> CartActivity 데이터 동일하게 연동

refactor: CartActivity LiveData 활용 리팩터링

refactor: DataBinding format 수정

fix: CartActivity 버그 수정

refactor: ProductListActivity Presenter 리팩터링

fix: ProductListActivity Presenter 리팩터링 및 버그 수정

refactor: CartPresenterTest 코드 수정에 맞게 리팩터링 및 수정

refactor: ProductDetailPresenterTest 리팩터링 및 수정

refactor: ProductListPresenterTest 수정, CartPresenterTest 테스트  통과 안되는 부분 수정

refactor: formatting

feat: 4단계 MockServer 구현 및 적용

refactor: 강제종료 버그 수정, 패키지 정리

fix: 최근 본 상품 반대로 나오는 버그 수정, ProductDetialActivity 다이얼로그 dismiss 추가

fix: 갯수 제한 걸리는 버그 수정

docs: README 업데이트
  • Loading branch information
woowahan-pjs authored and hyemdooly committed May 24, 2023
1 parent c0c96e0 commit f4a7519
Show file tree
Hide file tree
Showing 118 changed files with 4,710 additions and 1 deletion.
33 changes: 33 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Log/OS Files
*.log

# Android Studio generated files and folders
captures/
.externalNativeBuild/
.cxx/
*.apk
output.json

# IntelliJ
*.iml
.idea/
misc.xml
deploymentTargetDropDown.xml
render.experimental.xml

# Keystore files
*.jks
*.keystore

# Google Services (e.g. APIs or Firebase)
google-services.json

# Android Profiling
*.hprof
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,35 @@
# android-shopping-cart
# android-shopping-cart
## Domain
### ProductRepository
- [x] 상품 목록을 가져올 수 있다.
### CartRepository
- [x] 장바구니의 상품 목록을 가져올 수 있다.
- [x] 장바구니에 상품을 추가할 수 있다.
- [x] 장바구니에 상품을 삭제할 수 있다.
### Product
- name : 이름
- imageUrl : 이미지 URL
- price : 가격
### Price
- price : 가격
## View
- [x] 앱이 종료돼도 최근 본 상품 목록과 장바구니 데이터는 유지돼야 한다.
### ProductListActivity
- [x] 상품을 클릭하면 상품 상세로 이동한다.
- [X] 툴바 안의 카트 버튼을 누르면 장바구니로 이동한다.
- [x] 툴바 안의 카트 옆에 장바구니 상품 수가 노출된다.
- [x] 상품 목록의 +버튼을 누르면 장바구니에 상품이 추가된다.
- [x] +버튼을 누른 후 동시에 수량 선택 버튼이 노출된다.
- [x] 상품 목록의 상품 수가 변화하면 장바구니에 반영된다.
- [x] 장바구니 상품 수가 변화하면 상품 목록에도 반영된다.
### ProductDetailActivity
- [x] 사용자는 상품을 장바구니에 추가할 수 있다.
- [x] 마지막으로 본 상품 1개를 상품 상세 페이지에서 확인할 수 있다.
- [x] 마지막으로 본 상품을 선택했을 때는 마지막으로 본 상품이 보이지 않는다.
- [x] 마지막으로 본 상품 페이지에서 뒤로 가기를 하면 상품 목록으로 이동한다.
### CartActivity
- [x] 장바구니에서 원하는 상품을 삭제할 수 있다.
- [X] 툴바 안의 백버튼을 누르면 뒤로 이동한다.
- [x] 체크박스로 주문할 상품 범위를 조정할 할 수 있다.
- [x] 전체 체크박스를 선택하면 해당 페이지 내의 상품들만 선택된다.
- [x] 페이지가 바뀌어도 선택 항목은 유지된다.
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
56 changes: 56 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-parcelize")
}

android {
namespace = "woowacourse.shopping"
compileSdk = 33

defaultConfig {
applicationId = "woowacourse.shopping"
minSdk = 26
targetSdk = 33
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}

buildFeatures {
dataBinding = true
viewBinding = true
}
}

dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.0")
implementation("com.google.android.material:material:1.7.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation(project(":domain"))
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
implementation("com.github.bumptech.glide:glide:4.15.1")
testImplementation("io.mockk:mockk:1.13.5")
testImplementation("androidx.arch.core:core-testing:2.2.0")
}
21 changes: 21 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http:https://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Empty file.
35 changes: 35 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http:https://schemas.android.com/apk/res/android"
xmlns:tools="http:https://schemas.android.com/tools">

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

<application
android:usesCleartextTraffic="true"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.Shopping"
tools:targetApi="31">
<activity
android:name=".view.productdetail.ProductDetailActivity"
android:exported="false" />
<activity
android:name=".view.cart.CartActivity"
android:exported="false"
android:label="@string/label_cart" />
<activity
android:name=".view.productlist.ProductListActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package woowacourse.shopping.data.db

import android.provider.BaseColumns

object CartConstract : BaseColumns {
const val TABLE_NAME = "cart"
const val TABLE_COLUMN_ID = "id"
const val TABLE_COLUMN_COUNT = "count"
}
81 changes: 81 additions & 0 deletions app/src/main/java/woowacourse/shopping/data/db/CartDBHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package woowacourse.shopping.data.db

import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import woowacourse.shopping.domain.model.CartProduct

class CartDBHelper(context: Context) : SQLiteOpenHelper(context, "cart", null, 1) {
override fun onCreate(db: SQLiteDatabase?) {
db?.execSQL(
"CREATE TABLE ${CartConstract.TABLE_NAME} (" +
" ${CartConstract.TABLE_COLUMN_ID} Int PRIMARY KEY not null," +
" ${CartConstract.TABLE_COLUMN_COUNT} Int not null" +
");",
)
}

override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
db?.execSQL("DROP TABLE IF EXISTS ${CartConstract.TABLE_NAME}")
onCreate(db)
}

fun insert(id: Int, count: Int) {
val values = ContentValues()
values.put(CartConstract.TABLE_COLUMN_ID, id)
values.put(CartConstract.TABLE_COLUMN_COUNT, count)
writableDatabase.insert(CartConstract.TABLE_NAME, null, values)
}

fun update(id: Int, count: Int) {
writableDatabase.execSQL("UPDATE ${CartConstract.TABLE_NAME} SET ${CartConstract.TABLE_COLUMN_COUNT}=$count WHERE ${CartConstract.TABLE_COLUMN_ID}=$id")
}

fun remove(id: Int) {
writableDatabase.execSQL("DELETE FROM ${CartConstract.TABLE_NAME} WHERE ${CartConstract.TABLE_COLUMN_ID}=$id")
}

fun selectAll(): List<CartProduct> {
val products = mutableListOf<CartProduct>()
val sql = "select * from ${CartConstract.TABLE_NAME}"
val cursor = readableDatabase.rawQuery(sql, null)
while (cursor.moveToNext()) {
val id = cursor.getInt(cursor.getColumnIndexOrThrow(CartConstract.TABLE_COLUMN_ID))
val count = cursor.getInt(cursor.getColumnIndexOrThrow(CartConstract.TABLE_COLUMN_COUNT))
products.add(CartProduct(id, count))
}
cursor.close()
return products
}

fun selectWhereId(id: Int): CartProduct? {
val sql = "select * from ${CartConstract.TABLE_NAME} where ${CartConstract.TABLE_COLUMN_ID}=$id"
val cursor = readableDatabase.rawQuery(sql, null)
while (cursor.moveToNext()) {
val id = cursor.getInt(cursor.getColumnIndexOrThrow(CartConstract.TABLE_COLUMN_ID))
val count = cursor.getInt(cursor.getColumnIndexOrThrow(CartConstract.TABLE_COLUMN_COUNT))
cursor.close()
return CartProduct(id, count)
}
return null
}

fun selectRange(mark: Int, rangeSize: Int): List<CartProduct> {
val products = mutableListOf<CartProduct>()
val sql = "select * from ${CartConstract.TABLE_NAME} limit $rangeSize offset $mark;"
val cursor = readableDatabase.rawQuery(sql, null)
while (cursor.moveToNext()) {
val id = cursor.getInt(cursor.getColumnIndexOrThrow(CartConstract.TABLE_COLUMN_ID))
val count = cursor.getInt(cursor.getColumnIndexOrThrow(CartConstract.TABLE_COLUMN_COUNT))
products.add(CartProduct(id, count))
}
cursor.close()
return products
}

fun getSize(mark: Int): Boolean {
val itemsSize = selectAll().size
return mark in 0 until itemsSize
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package woowacourse.shopping.data.db

object RecentViewedContract {
const val TABLE_NAME = "recentViewed"
const val TABLE_COLUMN_ID = "id"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package woowacourse.shopping.data.db

import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper

class RecentViewedDBHelper(context: Context) : SQLiteOpenHelper(context, "recent_viewed", null, 1) {
override fun onCreate(db: SQLiteDatabase?) {
db?.execSQL(
"CREATE TABLE ${RecentViewedContract.TABLE_NAME} (" +
" ${RecentViewedContract.TABLE_COLUMN_ID} Int PRIMARY KEY not null" +
");",
)
}

override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
db?.execSQL("DROP TABLE IF EXISTS ${RecentViewedContract.TABLE_NAME}")
onCreate(db)
}

fun insert(id: Int) {
val values = ContentValues()
values.put(RecentViewedContract.TABLE_COLUMN_ID, id)
writableDatabase.insert(RecentViewedContract.TABLE_NAME, null, values)
}

fun remove(id: Int) {
writableDatabase.execSQL("DELETE FROM ${RecentViewedContract.TABLE_NAME} WHERE ${RecentViewedContract.TABLE_COLUMN_ID}=$id")
}

fun selectWhereId(id: Int): Int? {
val sql = "select * from ${RecentViewedContract.TABLE_NAME} WHERE ${RecentViewedContract.TABLE_COLUMN_ID}=$id"
val cursor = readableDatabase.rawQuery(sql, null)
while (cursor.moveToNext()) {
val id = cursor.getInt(cursor.getColumnIndexOrThrow(RecentViewedContract.TABLE_COLUMN_ID))
cursor.close()
return id
}
return null
}

fun selectAll(): List<Int> {
val viewedProducts = mutableListOf<Int>()
val sql = "select * from ${RecentViewedContract.TABLE_NAME}"
val cursor = readableDatabase.rawQuery(sql, null)
while (cursor.moveToNext()) {
val id = cursor.getInt(cursor.getColumnIndexOrThrow(RecentViewedContract.TABLE_COLUMN_ID))
viewedProducts.add(id)
}
cursor.close()
return viewedProducts
}

fun removeOldest() {
writableDatabase.execSQL("DELETE FROM ${RecentViewedContract.TABLE_NAME} WHERE rowid = (SELECT MIN(rowid) FROM ${RecentViewedContract.TABLE_NAME});")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package woowacourse.shopping.data.repository

import android.content.Context
import woowacourse.shopping.data.db.CartDBHelper
import woowacourse.shopping.domain.model.CartProduct
import woowacourse.shopping.domain.repository.CartRepository

class CartDbRepository(context: Context) : CartRepository {
private val dbHelper = CartDBHelper(context)
override fun findAll(): List<CartProduct> {
return dbHelper.selectAll()
}

override fun find(id: Int): CartProduct? {
return dbHelper.selectWhereId(id)
}

override fun add(id: Int, count: Int) {
val cardProduct = find(id)
if (cardProduct != null) {
dbHelper.update(id, count + cardProduct.count)
return
}
dbHelper.insert(id, count)
}

override fun update(id: Int, count: Int) {
dbHelper.update(id, count)
}

override fun remove(id: Int) {
dbHelper.remove(id)
}

override fun findRange(mark: Int, rangeSize: Int): List<CartProduct> {
return dbHelper.selectRange(mark, rangeSize)
}

override fun isExistByMark(mark: Int): Boolean {
return dbHelper.getSize(mark)
}
}
Loading

0 comments on commit f4a7519

Please sign in to comment.