Skip to content
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

[둘리] 1, 2단계 영화 극장 선택 제출합니다. #4

Merged
merged 32 commits into from
Apr 29, 2023
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e0afc94
영화 티켓 예매 미션
woowahan-pjs Apr 5, 2023
7026a40
feat(MovieListFragment): 영화 목록 화면 fragment로 변경
EmilyCh0 Apr 25, 2023
74deaf3
feat(MovieMainActivity): BottomNavigationView 구현
hyemdooly Apr 25, 2023
b2ef721
feat(ReservationListFragment): 예매 내역 레이아웃 구현
EmilyCh0 Apr 25, 2023
8135b14
feat(ReservationListFragment): RecyclerView 구현
hyemdooly Apr 25, 2023
4f8255a
feat(ReservationListFragment): 클릭 시 예매 내역 상세 화면으로 이동 구현
EmilyCh0 Apr 25, 2023
add9dd3
feat(ReservationListFragment): Data 저장 구현
hyemdooly Apr 25, 2023
40fcc29
refactor(MovieListAdapter): 클릭 이벤트 콜백 하나로 합치기
EmilyCh0 Apr 25, 2023
44bf562
feat(AlarmReceiver): 예매 후 상영 전 시간 푸시 알림 구현
hyemdooly Apr 26, 2023
cca69f7
feat(SettingFragment, AlarmController): 푸시 알림 수신 설정 구현
EmilyCh0 Apr 26, 2023
a508eaa
refactor(AlarmController): 알림 받을 시간 수정
EmilyCh0 Apr 26, 2023
1592c3d
refactor(AlarmController, AlarmReceiver): 코드 정리 및 메서드 분리
EmilyCh0 Apr 27, 2023
f5a9ef9
test(MovieMainActivityTest): 설정 프래그먼트 테스트
hyemdooly Apr 27, 2023
9737d18
refactor(AndroidManifest): 필요없는 권한 삭제
hyemdooly Apr 28, 2023
939d84a
refactor(layout): xml formatting
hyemdooly Apr 28, 2023
76e9b00
test(SettingFragmentTest): 설정 프래그먼트 테스트
hyemdooly Apr 28, 2023
beb0b3a
test(MovieMainActivityTest): Fragment 선택 테스트
hyemdooly Apr 28, 2023
b71eb97
refactor(reservation_item.xml): isFocusable, isClickable 설정 삭제
hyemdooly Apr 28, 2023
7a8b6b9
refactor(SettingFragment): property formatting
hyemdooly Apr 28, 2023
070cce4
refactor(SettingFragment): if문 depth 수정
hyemdooly Apr 28, 2023
8497d53
refactor(Adapter, ViewHolder): click listener 파라미터 고차함수 -> OnItemClic…
hyemdooly Apr 28, 2023
b2ce59d
refactor(MovieListFragment): click listener 분리
hyemdooly Apr 28, 2023
fcd527f
refactor(ReservationListFragment): 변수 분리 가독성 수정
hyemdooly Apr 28, 2023
c9df85d
refactor(ReservationCompletedActivity): 권한 유도 코드 이동
hyemdooly Apr 28, 2023
8eba6d0
refactor(AlarmController): context private 수정
hyemdooly Apr 28, 2023
085e9dd
refactor(AlarmReceiver, AlarmController): ALARM_REQUEST_CODE 상수 이동
hyemdooly Apr 28, 2023
fd3e4d3
refactor(ReservationCompletedActivity): pendingIntent를 리턴하는 함수 생성
hyemdooly Apr 28, 2023
17cafbc
refactor(SeatSelectionActivity): repositoryMock에 직접 접근이 아닌 interface …
hyemdooly Apr 28, 2023
b634aa8
refactor(SettingFragment): defaultSharedPreferences로 변경
hyemdooly Apr 28, 2023
72e7a58
refactor(Fragments): 레이아웃 id 생성자 사용
hyemdooly Apr 28, 2023
00f4a8f
refactor(MovieMainActivity): Fragment 재활용
hyemdooly Apr 28, 2023
923eedd
refactor(ReservationCompletedActivity): 권한 요청 코드 이동
hyemdooly Apr 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
<<<<<<< HEAD
laco-dev marked this conversation as resolved.
Show resolved Hide resolved
# android-movie-theater
=======
# android-movie-ticket

## domain

Expand Down
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ dependencies {
implementation("androidx.appcompat:appcompat:1.6.0")
implementation("com.google.android.material:material:1.7.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.fragment:fragment-ktx:1.4.0")
implementation("androidx.preference:preference:1.2.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package woowacourse.movie.view

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import woowacourse.movie.R
import woowacourse.movie.view.moviemain.MovieMainActivity

@RunWith(AndroidJUnit4::class)
class MovieMainActivityTest {
@get:Rule
val mActivityTestRule = ActivityScenarioRule(MovieMainActivity::class.java)

@Test
fun 예매내역_버튼을_누르면_예매내역_Fragment로_바뀐다() {
onView(withId(R.id.action_reservation_list)).perform(click())
onView(withId(R.id.recyclerview)).check(matches(isDisplayed()))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

프래그먼트가 보인다를 테스트해야 하는데, 실제로 해당 프래그먼트의 컨텐츠를 확인하는게 아쉽네요..

저도 프래그먼트 테스트는 자주 해보지 않아서, 어떤 방식이 최적의 방식인지는 추천드리기 어려운데
프래그먼트 매니저를 이용해서 프래그먼트의 인스턴스가 해당 타입이 맞는지 확인하는게 가능할까 싶네요!?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onView(withId(R.id.action_setting)).perform(click())
mActivityTestRule.scenario.onActivity {
        val fragment = it.supportFragmentManager.findFragmentByTag(SettingFragment.TAG_SETTING)
        assertTrue(fragment != null && fragment.isVisible)
}

TAG로 프래그먼트를 가져와서 그 프래그먼트가 visible인지 검사했습니다! :)

}

@Test
fun 홈_버튼을_누르면_홈_Fragment로_바뀐다() {
onView(withId(R.id.action_home)).perform(click())
onView(withId(R.id.movie_recyclerview)).check(matches(isDisplayed()))
}

@Test
fun 설정_버튼을_누르면_설정_Fragment로_바뀐다() {
onView(withId(R.id.action_setting)).perform(click())
onView(withId(R.id.setting_toggle)).check(matches(isDisplayed()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import woowacourse.movie.domain.Minute
import woowacourse.movie.domain.Movie
import woowacourse.movie.view.mapper.toUiModel
import woowacourse.movie.view.model.ReservationOptions
import woowacourse.movie.view.seatselection.SeatSelectionActivity
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package woowacourse.movie.view

import android.content.Context
import android.content.SharedPreferences
import androidx.fragment.app.commit
import androidx.preference.PreferenceManager
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import woowacourse.movie.R
import woowacourse.movie.view.moviemain.MovieMainActivity
import woowacourse.movie.view.moviemain.setting.SettingFragment

@RunWith(AndroidJUnit4::class)
class SettingFragmentTest {
@get:Rule
val mActivityTestRule = ActivityScenarioRule(MovieMainActivity::class.java)

@Before
fun setup() {
ActivityScenario.launch(MovieMainActivity::class.java).onActivity {
it.supportFragmentManager.commit {
replace(R.id.fragment_container_view, SettingFragment())
}
}
}

@Test
fun 설정_Fragment에서_저장된_세팅값이_false면_토글도_꺼져있다() {
setSharedPreferences(false)
onView(ViewMatchers.withId(R.id.action_setting)).perform(ViewActions.click())
onView(ViewMatchers.withId(R.id.setting_toggle))
.check(ViewAssertions.matches(ViewMatchers.isNotChecked()))
}

@Test
fun 설정_Fragment에서_저장된_세팅값이_true면_토글도_켜져있다() {
setSharedPreferences(true)
onView(ViewMatchers.withId(R.id.action_setting)).perform(ViewActions.click())
onView(ViewMatchers.withId(R.id.setting_toggle))
.check(ViewAssertions.matches(ViewMatchers.isChecked()))
}

private fun setSharedPreferences(isAlarmOn: Boolean) {
val targetContext: Context = InstrumentationRegistry.getInstrumentation().targetContext

val preferencesEditor: SharedPreferences.Editor = PreferenceManager.getDefaultSharedPreferences(targetContext).edit()
preferencesEditor.clear()
preferencesEditor.putBoolean(SettingFragment.IS_ALARM_ON, isAlarmOn)
preferencesEditor.commit()
}
Comment on lines +53 to +60

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아래 내용은 꼭 반영하지 않으셔도 됩니다.

세팅 화면에서 UI 테스트를 하기 위해 프리퍼런스가 잘 동작하는지를 체크하는게 맞을까? 라는 의문이 들었어요.

만약 프리퍼런스가 아닌 데이터베이스로 바뀌면 UI 테스트가 같이 실패하게 됩니다.
객체지향 관점으로 접근해 보면, 어떤 방법으로 데이터를 가져오는지 관심 없도록 만들어 볼 수도 있겠네요

}
18 changes: 12 additions & 6 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools">
xmlns:tools="https://schemas.android.com/tools" >

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

<application
android:allowBackup="true"
Expand All @@ -12,9 +12,15 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.Movie"
tools:targetApi="31">
tools:targetApi="31" >
<receiver
android:name=".view.seatselection.AlarmReceiver"
android:enabled="true"
android:exported="true" >
</receiver>

<activity
android:name=".view.SeatSelectionActivity"
android:name=".view.seatselection.SeatSelectionActivity"
android:exported="false" />
<activity
android:name=".view.ReservationCompletedActivity"
Expand All @@ -23,8 +29,8 @@
android:name=".view.ReservationActivity"
android:exported="false" />
<activity
android:name=".view.MovieListActivity"
android:exported="true">
android:name=".view.moviemain.MovieMainActivity"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package woowacourse.movie.repository
package woowacourse.movie.data

import woowacourse.movie.R
import woowacourse.movie.domain.Minute
import woowacourse.movie.domain.Movie
import woowacourse.movie.domain.repository.MovieRepository
import java.time.LocalDate

object MovieMockRepository : MovieRepository {
Expand Down Expand Up @@ -45,6 +46,6 @@ object MovieMockRepository : MovieRepository {
}.flatten()

override fun findAll(): List<Movie> {
return movies
return movies.toList()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package woowacourse.movie.data

import woowacourse.movie.domain.Reservation
import woowacourse.movie.domain.repository.ReservationRepository

object ReservationMockRepository : ReservationRepository {

private val reservations = mutableListOf<Reservation>()
override fun add(reservation: Reservation) {
reservations.add(reservation)
}

override fun findAll(): List<Reservation> {
return reservations.toList()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ package woowacourse.movie.util
import java.time.format.DateTimeFormatter

val DATE_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd")
val DATE_TIME_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm")
val TIME_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
60 changes: 60 additions & 0 deletions app/src/main/java/woowacourse/movie/view/AlarmController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package woowacourse.movie.view

import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import woowacourse.movie.view.model.ReservationUiModel
import woowacourse.movie.view.seatselection.AlarmReceiver
import java.time.ZoneId

class AlarmController(
private val context: Context
) {

fun registerAlarms(reservations: List<ReservationUiModel>, minuteInterval: Long) {
reservations.forEach {
registerAlarm(it, minuteInterval)
}
}

fun registerAlarm(reservation: ReservationUiModel, minuteInterval: Long) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val pendingIntent = getPendingIntent(reservation)

alarmManager.set(
AlarmManager.RTC_WAKEUP,
reservation.screeningDateTime.minusMinutes(minuteInterval)
.atZone(ZoneId.systemDefault())
.toEpochSecond() * 1000L,
pendingIntent,
)
}

private fun getPendingIntent(reservation: ReservationUiModel): PendingIntent {
return Intent(context, AlarmReceiver::class.java).let {
it.putExtra(AlarmReceiver.RESERVATION, reservation)
PendingIntent.getBroadcast(
context,
ALARM_REQUEST_CODE,
it,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)
}
Comment on lines +35 to +43

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let에 대한 올바른 사용 방법이 아닙니다.

코틀린에서 제공하는 범위 함수를 어떤 상황에서 사용하는지는 조금씩 다르지만, 단순히 변수 줄이는 목적처럼 사용하지는 않습니다.

https://kotlinlang.org/docs/scope-functions.html

}

fun cancelAlarms() {
val pendingIntent = PendingIntent.getBroadcast(
context,
ALARM_REQUEST_CODE,
Intent(context, AlarmReceiver::class.java),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
Comment on lines +47 to +52

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반복되는 코드가 보이면 어떻게 해야 한다!?

val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.cancel(pendingIntent)
}

companion object {
private const val ALARM_REQUEST_CODE = 100
}
}
60 changes: 0 additions & 60 deletions app/src/main/java/woowacourse/movie/view/MovieListActivity.kt

This file was deleted.

13 changes: 7 additions & 6 deletions app/src/main/java/woowacourse/movie/view/ReservationActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import woowacourse.movie.util.getParcelableCompat
import woowacourse.movie.util.getSerializableCompat
import woowacourse.movie.view.model.MovieListModel.MovieUiModel
import woowacourse.movie.view.model.ReservationOptions
import woowacourse.movie.view.seatselection.SeatSelectionActivity
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
Expand Down Expand Up @@ -60,7 +61,7 @@ class ReservationActivity : AppCompatActivity() {
movieTitle.text = movie.title
movieScreeningDate.text = getString(R.string.screening_date_format).format(
movie.screeningStartDate.format(DATE_FORMATTER),
movie.screeningEndDate.format(DATE_FORMATTER)
movie.screeningEndDate.format(DATE_FORMATTER),
)
movieRunningTime.text =
getString(R.string.running_time_format).format(movie.runningTime)
Expand All @@ -77,7 +78,7 @@ class ReservationActivity : AppCompatActivity() {
val dateSpinnerAdapter = ArrayAdapter(
this,
android.R.layout.simple_spinner_item,
screeningDates
screeningDates,
)

binding.dateSpinner.apply {
Expand All @@ -87,7 +88,7 @@ class ReservationActivity : AppCompatActivity() {
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
id: Long,
) {
selectedScreeningDate = screeningDates[position]
initTimeSpinner(timeSpinnerPosition)
Expand All @@ -104,7 +105,7 @@ class ReservationActivity : AppCompatActivity() {
val timeSpinnerAdapter = ArrayAdapter(
this,
android.R.layout.simple_spinner_item,
screeningTimes
screeningTimes,
)
binding.timeSpinner.apply {
adapter = timeSpinnerAdapter
Expand All @@ -117,7 +118,7 @@ class ReservationActivity : AppCompatActivity() {
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
id: Long,
) {
selectedScreeningTime = screeningTimes[position]
}
Expand Down Expand Up @@ -157,7 +158,7 @@ class ReservationActivity : AppCompatActivity() {
val reservationOptions = ReservationOptions(
movie.title,
LocalDateTime.of(selectedScreeningDate, selectedScreeningTime),
peopleCountSaved
peopleCountSaved,
)
startActivity(SeatSelectionActivity.newIntent(this, reservationOptions, movie))
}
Expand Down
Loading