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 1 commit
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
Prev Previous commit
Next Next commit
feat(AlarmReceiver): 예매 후 상영 전 시간 푸시 알림 구현
  • Loading branch information
hyemdooly committed Apr 26, 2023
commit 44bf56296b8fe84fd66cc8560313de53833f792d
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
17 changes: 12 additions & 5 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<?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">
xmlns:tools="http:https://schemas.android.com/tools" >

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.INTERNET" />
laco-dev marked this conversation as resolved.
Show resolved Hide resolved
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application
android:allowBackup="true"
Expand All @@ -12,9 +13,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 @@ -24,7 +31,7 @@
android:exported="false" />
<activity
android:name=".view.moviemain.MovieMainActivity"
android:exported="true">
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class ReservationCompletedActivity : AppCompatActivity() {
companion object {
private const val RESERVATION = "RESERVATION"
private val DECIMAL_FORMAT = DecimalFormat("#,###")
const val REQUEST_CODE = 101

fun newIntent(context: Context, reservation: ReservationUiModel): Intent {
val intent = Intent(context, ReservationCompletedActivity::class.java)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
package woowacourse.movie.view.moviemain

import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import com.google.android.material.bottomnavigation.BottomNavigationView
import woowacourse.movie.R
import woowacourse.movie.view.moviemain.movielist.MovieListFragment
import woowacourse.movie.view.moviemain.reservationlist.ReservationListFragment
import woowacourse.movie.view.moviemain.setting.SettingFragment
import woowacourse.movie.view.seatselection.AlarmReceiver

class MovieMainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_movie_main)

createChannel()

requestNotificationPermission()
laco-dev marked this conversation as resolved.
Show resolved Hide resolved

val navigation = findViewById<BottomNavigationView>(R.id.navigation_view)

navigation.setOnItemSelectedListener { item ->
Expand Down Expand Up @@ -43,4 +57,38 @@ class MovieMainActivity : AppCompatActivity() {
replace(R.id.fragment_container_view, fragment)
}
}

private fun createChannel() {
val name = "Reservation Notification"
val channel = NotificationChannel(
AlarmReceiver.CHANNEL_ID,
name,
NotificationManager.IMPORTANCE_DEFAULT,
)
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}

private fun requestNotificationPermission() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS,
) != PackageManager.PERMISSION_GRANTED
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
Toast.makeText(this, "권한을 설정해주셔야 알람을 받으실 수 있습니다.", Toast.LENGTH_LONG).show()
} else {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}
}

private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission(),
) { isGranted: Boolean ->
if (isGranted) Toast.makeText(this, "권한을 설정해주셨습니다!", Toast.LENGTH_LONG).show()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package woowacourse.movie.view.seatselection

import android.Manifest
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import woowacourse.movie.R
import woowacourse.movie.util.getParcelableCompat
import woowacourse.movie.view.ReservationCompletedActivity
import woowacourse.movie.view.model.ReservationUiModel

class AlarmReceiver : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent) {
val reservation = intent.getParcelableCompat<ReservationUiModel>(RESERVATION)
val content = reservation?.let {
context.getString(R.string.screening_after_30_minutes, reservation.title)
}
val toReservationCompletedIntent = reservation?.let {
ReservationCompletedActivity.newIntent(context, reservation)
}
val pendingIntent = PendingIntent.getActivity(
context,
ReservationCompletedActivity.REQUEST_CODE,
toReservationCompletedIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)
laco-dev marked this conversation as resolved.
Show resolved Hide resolved
val builder = NotificationCompat.Builder(context, CHANNEL_ID).apply {
setSmallIcon(R.drawable.ic_movie)
setContentTitle(context.getString(R.string.reservation_noti))
setContentText(content)
setContentIntent(pendingIntent)
setAutoCancel(true)
}
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS,
) == PackageManager.PERMISSION_GRANTED
) {
NotificationManagerCompat.from(context).notify(NOTI_ID, builder.build())
return
}
}

companion object {
const val NOTI_ID = 200
const val CHANNEL_ID = "RESERVATION_CHANNEL"
const val RESERVATION = "RESERVATION"
const val REQUEST_CODE = "REQUEST_CODE"
const val ALARM_REQUEST_CODE = 100
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package woowacourse.movie.view
package woowacourse.movie.view.seatselection

import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.MenuItem
import android.widget.Button
import android.widget.TableLayout
Expand All @@ -19,12 +22,16 @@ import woowacourse.movie.databinding.ActivitySeatSelectionBinding
import woowacourse.movie.domain.ReservationAgency
import woowacourse.movie.domain.Seat
import woowacourse.movie.util.getParcelableCompat
import woowacourse.movie.view.ReservationCompletedActivity
import woowacourse.movie.view.mapper.toDomainModel
import woowacourse.movie.view.mapper.toUiModel
import woowacourse.movie.view.model.MovieListModel.MovieUiModel
import woowacourse.movie.view.model.ReservationOptions
import woowacourse.movie.view.model.ReservationUiModel
import woowacourse.movie.view.model.SeatUiModel
import java.text.DecimalFormat
import java.time.LocalDateTime
import java.time.ZoneId

class SeatSelectionActivity : AppCompatActivity() {

Expand Down Expand Up @@ -84,7 +91,7 @@ class SeatSelectionActivity : AppCompatActivity() {
seat.isSelected = false
binding.confirmReservationButton.isEnabled = false
binding.reservationFeeTextview.text = getString(R.string.reservation_fee_format).format(
DECIMAL_FORMAT.format(0)
DECIMAL_FORMAT.format(0),
)
}

Expand Down Expand Up @@ -123,24 +130,24 @@ class SeatSelectionActivity : AppCompatActivity() {
selectedSeats.add(
Seat(
index % Seat.MAX_COLUMN + 1,
index / Seat.MAX_COLUMN + 1
)
index / Seat.MAX_COLUMN + 1,
),
)
}
return selectedSeats
}

private fun setReservationFee(fee: Int) {
binding.reservationFeeTextview.text = getString(R.string.reservation_fee_format).format(
DECIMAL_FORMAT.format(fee)
DECIMAL_FORMAT.format(fee),
)
}

private fun initReserveLayout() {
binding.apply {
movieTitleTextview.text = reservationOptions?.title
reservationFeeTextview.text = getString(R.string.reservation_fee_format).format(
DECIMAL_FORMAT.format(0)
DECIMAL_FORMAT.format(0),
)
confirmReservationButton.isEnabled = false
}
Expand All @@ -154,7 +161,7 @@ class SeatSelectionActivity : AppCompatActivity() {
reservationAgency = ReservationAgency(
movie,
reservationOptions!!.peopleCount,
reservationOptions!!.screeningDateTime
reservationOptions!!.screeningDateTime,
)
}
}
Expand All @@ -180,10 +187,32 @@ class SeatSelectionActivity : AppCompatActivity() {
val reservation = reservationAgency.reserve(selectedSeats)
reservation?.let {
ReservationMockRepository.add(reservation)
laco-dev marked this conversation as resolved.
Show resolved Hide resolved
registerAlarm(reservation.toUiModel())
startActivity(ReservationCompletedActivity.newIntent(this, reservation.toUiModel()))
}
}

private fun registerAlarm(reservation: ReservationUiModel) {
val alarmManager = this.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val pendingIntent = Intent(this, AlarmReceiver::class.java).let {
it.putExtra(AlarmReceiver.REQUEST_CODE, AlarmReceiver.ALARM_REQUEST_CODE)
it.putExtra(AlarmReceiver.RESERVATION, reservation)
PendingIntent.getBroadcast(
this,
AlarmReceiver.ALARM_REQUEST_CODE,
it,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)
}

Log.d("manager", "set completed")
alarmManager.set(
AlarmManager.RTC_WAKEUP,
LocalDateTime.now().plusSeconds(3).atZone(ZoneId.systemDefault()).toEpochSecond() * 1000L,
pendingIntent,
)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> finish()
Expand All @@ -195,11 +224,13 @@ class SeatSelectionActivity : AppCompatActivity() {
private const val RESERVATION_OPTIONS = "RESERVATION_OPTIONS"
private const val MOVIE = "MOVIE"
private val DECIMAL_FORMAT = DecimalFormat("#,###")
private const val RESERVATION = "RESERVATION"
private const val REQUEST_CODE = "REQUEST_CODE"

fun newIntent(
context: Context,
reservationOptions: ReservationOptions,
movie: MovieUiModel
movie: MovieUiModel,
): Intent {
val intent = Intent(context, SeatSelectionActivity::class.java)
intent.putExtra(RESERVATION_OPTIONS, reservationOptions)
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/ic_movie.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http:https://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M18,3v2h-2L16,3L8,3v2L6,5L6,3L4,3v18h2v-2h2v2h8v-2h2v2h2L20,3h-2zM8,17L6,17v-2h2v2zM8,13L6,13v-2h2v2zM8,9L6,9L6,7h2v2zM18,17h-2v-2h2v2zM18,13h-2v-2h2v2zM18,9h-2L16,7h2v2z"/>
</vector>
2 changes: 1 addition & 1 deletion app/src/main/res/layout/activity_seat_selection.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/light_gray"
tools:context=".view.SeatSelectionActivity">
tools:context=".view.seatselection.SeatSelectionActivity">

<TextView
android:id="@+id/screen_textview"
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@
<string name="setting">설정</string>
<string name="datetime_with_space">%s %s</string>
<string name="datetime_with_line">%s | %s</string>
<string name="reservation_noti">예매 알림</string>
<string name="screening_after_30_minutes">%s 30분 후에 상영</string>
</resources>