-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 러닝 화면에 일시정지/재개 기능 추가 #406
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
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -97,6 +97,7 @@ class RunActivity : BindingActivity<ActivityRunBinding>(R.layout.activity_run), | |||||||||||||||||||||||||||||||||||||||||||||||
| getCurrentLocation() | ||||||||||||||||||||||||||||||||||||||||||||||||
| showRecord() | ||||||||||||||||||||||||||||||||||||||||||||||||
| backButton() | ||||||||||||||||||||||||||||||||||||||||||||||||
| setUpPauseResume() | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| val runCourseData: CourseData? = intent.getParcelableExtra(EXTRA_COUNTDOWN_TO_RUN) | ||||||||||||||||||||||||||||||||||||||||||||||||
| val targetDistanceM = runCourseData?.distance?.let { (it * 1000f).roundToInt() } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -133,6 +134,30 @@ class RunActivity : BindingActivity<ActivityRunBinding>(R.layout.activity_run), | |||||||||||||||||||||||||||||||||||||||||||||||
| stopService(serviceIntent) //서비스 객체 제거 | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| private fun setUpPauseResume() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| binding.btnRunPauseResume.setOnClickListener { | ||||||||||||||||||||||||||||||||||||||||||||||||
| val isPaused = viewModel.isPaused.value ?: false | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (isPaused) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| timerService?.resumeTimer() | ||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||
| timerService?.pauseTimer() | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| viewModel.isPaused.value = !isPaused | ||||||||||||||||||||||||||||||||||||||||||||||||
| updatePauseResumeUI(!isPaused) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+137
to
+146
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== locate files =="
fd -a 'RunActivity\.kt$|TimerService\.kt$' . | sed 's#^\./##'
echo "== RunActivity outline/sections =="
RUNACTIVITY="$(fd 'RunActivity\.kt$' . | head -n1 || true)"
if [ -n "${RUNACTIVITY:-}" ]; then
echo "--- $RUNACTIVITY lines 1-230 ---"
sed -n '1,230p' "$RUNACTIVITY" | nl -ba
fi
echo "== TimerService outline/sections =="
TIMER="$(fd 'TimerService\.kt$' . | head -n1 || true)"
if [ -n "${TIMER:-}" ]; then
echo "--- $TIMER lines 1-260 ---"
sed -n '1,260p' "$TIMER" | nl -ba
fi
echo "== references to timerService/isPaused/pauseResume/updatePauseResumeUI =="
rg -n "timerService|isPaused|pauseResume|updatePauseResumeUI|onServiceConnected|bindService|UnbindService|btnRunPauseResume" app/src/main/java || trueRepository: Runnect/Runnect-Android Length of output: 628 🏁 Script executed: #!/bin/bash
set -euo pipefail
RUNACTIVITY="$(fd 'RunActivity\.kt$' . | grep 'app/src/main/java/com/runnect/runnect/presentation/run/RunActivity.kt' || true)"
TIMER="$(fd 'TimerService\.kt$' . | grep 'app/src/main/java/com/runnect/runnect/presentation/run/TimerService.kt' || true)"
echo "== RunActivity relevant sections =="
sed -n '1,240p' "${RUNACTIVITY:-/dev/null}"
echo
echo "== TimerService relevant sections =="
sed -n '1,280p' "${TIMER:-/dev/null}"
echo
echo "== references =="
rg -n "timerService|isPaused|setUpPauseResume|updatePauseResumeUI|onServiceConnected|bindService|bindServiceAsync|UnbindService|btnRunPauseResume|pauseTimer|resumeTimer" app/src/main/java || true
echo
echo "== Kotlin safe-call behavior probe =="
node - <<'JS'
let timerServiceCalled = false;
class TimerService {
pauseTimer() {
timerServiceCalled = true;
console.log("pauseTimer called");
}
resumeTimer() {
console.log("resumeTimer called");
}
}
let timerService = null;
let viewModelIsPaused = false;
// Kotlin safe-call null behavior: if receiver is null, expression returns null and body is skipped.
try {
if (timerService != null) {
timerService.pauseTimer();
}
} catch (e) {
console.error(e);
}
viewModelIsPaused = !viewModelIsPaused;
console.log({timerServiceCalled, viewModelIsPaused});
timerService = new TimerService();
if (timerService != null) {
timerService.resumeTimer();
}
console.log({timerServiceCalled});
JSRepository: Runnect/Runnect-Android Length of output: 17308 Return before updating pause state if
Check Proposed fix private fun setUpPauseResume() {
binding.btnRunPauseResume.setOnClickListener {
+ val service = timerService ?: return@setOnClickListener
val isPaused = viewModel.isPaused.value ?: false
if (isPaused) {
- timerService?.resumeTimer()
+ service.resumeTimer()
} else {
- timerService?.pauseTimer()
+ service.pauseTimer()
}
viewModel.isPaused.value = !isPaused
updatePauseResumeUI(!isPaused)
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| private fun updatePauseResumeUI(isPaused: Boolean) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| binding.btnRunPauseResume.apply { | ||||||||||||||||||||||||||||||||||||||||||||||||
| setImageResource(if (isPaused) R.drawable.ic_run_resume else R.drawable.ic_run_pause) | ||||||||||||||||||||||||||||||||||||||||||||||||
| contentDescription = getString( | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (isPaused) R.string.run_description_resume else R.string.run_description_pause | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| binding.btnRunStop.visibility = if (isPaused) View.VISIBLE else View.GONE | ||||||||||||||||||||||||||||||||||||||||||||||||
| binding.tvPausedLabel.visibility = if (isPaused) View.VISIBLE else View.GONE | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| override fun onStart() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| super.onStart() | ||||||||||||||||||||||||||||||||||||||||||||||||
| // Timer 결과값을 받기 위해 브로드캐스트 리시버 등록 | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -336,7 +361,7 @@ class RunActivity : BindingActivity<ActivityRunBinding>(R.layout.activity_run), | |||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| private fun showRecord() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| binding.btnRunFinish.setOnClickListener { | ||||||||||||||||||||||||||||||||||||||||||||||||
| binding.btnRunStop.setOnClickListener { | ||||||||||||||||||||||||||||||||||||||||||||||||
| stopTimer() | ||||||||||||||||||||||||||||||||||||||||||||||||
| val totalTimeSec = ((timerData.hour ?: 0) * 3600) + ((timerData.minute ?: 0) * 60) + (timerData.second ?: 0) | ||||||||||||||||||||||||||||||||||||||||||||||||
| Analytics.logEvent( | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <shape xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:shape="rectangle"> | ||
| <solid android:color="#D1171717" /> | ||
| <corners android:radius="999dp" /> | ||
| </shape> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <shape xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:shape="oval"> | ||
| <solid android:color="@color/W1" /> | ||
| <stroke | ||
| android:width="1.5dp" | ||
| android:color="@color/G4" /> | ||
| </shape> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <shape xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:shape="oval"> | ||
| <solid android:color="@color/M1" /> | ||
| </shape> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:width="24dp" | ||
| android:height="24dp" | ||
| android:viewportWidth="24" | ||
| android:viewportHeight="24"> | ||
| <path | ||
| android:pathData="M6,5h4v14h-4z" | ||
| android:fillColor="@color/W1" /> | ||
| <path | ||
| android:pathData="M14,5h4v14h-4z" | ||
| android:fillColor="@color/W1" /> | ||
| </vector> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:width="24dp" | ||
| android:height="24dp" | ||
| android:viewportWidth="24" | ||
| android:viewportHeight="24"> | ||
| <path | ||
| android:pathData="M7,4.5v15l13,-7.5z" | ||
| android:fillColor="@color/W1" /> | ||
| </vector> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:width="20dp" | ||
| android:height="20dp" | ||
| android:viewportWidth="20" | ||
| android:viewportHeight="20"> | ||
| <path | ||
| android:pathData="M4,4h12v12h-12z" | ||
| android:fillColor="@color/G1" /> | ||
| </vector> |
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.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Runnect/Runnect-Android
Length of output: 3001
🏁 Script executed:
Repository: Runnect/Runnect-Android
Length of output: 8933
🏁 Script executed:
Repository: Runnect/Runnect-Android
Length of output: 6404
Render the retained pause state when the activity is recreated.
RunViewModel.isPausedsurvives configuration changes, butupdatePauseResumeUI()only runs from the pause/resume button callback. After rotation during a pause, the recreated UI shows the running icon while the timer service remains paused.Read
isPausedinonCreate()from the retained ViewModel value and callupdatePauseResumeUI()for that value before setting up the button listener.🤖 Prompt for AI Agents