-
Notifications
You must be signed in to change notification settings - Fork 0
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
[Scrum 44] 뽀모도르 알림 UI/UX 개선 #145
Changes from all commits
46f2543
8127d62
6a4f169
2777d28
a2214c9
08a7a3a
3584b60
143f69b
5863691
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 |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package com.pomonyang.mohanyang.presentation.noti | ||
|
||
import android.content.Context | ||
import android.graphics.Bitmap | ||
import android.graphics.Canvas | ||
import android.graphics.Paint | ||
import android.graphics.Typeface | ||
import androidx.annotation.ColorRes | ||
import androidx.annotation.FontRes | ||
import androidx.annotation.StringRes | ||
import androidx.core.content.ContextCompat | ||
import androidx.core.content.res.ResourcesCompat | ||
import com.mohanyang.presentation.R | ||
import dagger.hilt.android.qualifiers.ApplicationContext | ||
import javax.inject.Inject | ||
|
||
internal class PomodoroNotificationBitmapGenerator @Inject constructor( | ||
@ApplicationContext private val context: Context | ||
) { | ||
Comment on lines
+17
to
+19
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.
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. ..오.... 세상에나...... 내 문해력이 부족해서 잘못 이해하고 있나 했어 진짜 많은 일이 있었구나.... |
||
|
||
private val timeBitmaps: Map<String, Bitmap> by lazy { | ||
generateNumberBitmaps( | ||
fontResId = R.font.pretendard_bold, | ||
textSize = 40f, | ||
colorResId = R.color.notification_pomodoro_time | ||
) | ||
} | ||
|
||
private val overtimeNumberBitmaps: Map<String, Bitmap> by lazy { | ||
generateNumberBitmaps( | ||
fontResId = R.font.pretendard_semibold, | ||
textSize = 18f, | ||
colorResId = R.color.notification_pomodoro_over_time | ||
) | ||
} | ||
|
||
private val colonBitmap: Bitmap by lazy { | ||
createTextBitmap( | ||
text = ":", | ||
fontResId = R.font.pretendard_bold, | ||
textSize = 40f, | ||
colorResId = R.color.notification_pomodoro_time | ||
) | ||
} | ||
|
||
private val overtimeColonBitmap: Bitmap by lazy { | ||
createTextBitmap( | ||
text = ":", | ||
fontResId = R.font.pretendard_semibold, | ||
textSize = 18f, | ||
colorResId = R.color.notification_pomodoro_over_time | ||
) | ||
} | ||
|
||
fun createStatusBitmap(statusText: String): Bitmap = createTextBitmap( | ||
text = statusText, | ||
fontResId = R.font.pretendard_semibold, | ||
textSize = 16f, | ||
colorResId = R.color.notification_pomodoro_category_text | ||
) | ||
|
||
fun combineTimeBitmaps(time: String, isOvertime: Boolean): Bitmap { | ||
val digits = time.toCharArray() | ||
return combineBitmaps( | ||
digits = digits, | ||
isOvertime = isOvertime | ||
) | ||
} | ||
|
||
fun createTextBitmap( | ||
@StringRes text: Int, | ||
@ColorRes color: Int, | ||
@FontRes font: Int, | ||
textSize: Float | ||
): Bitmap = createTextBitmap( | ||
text = context.getString(text), | ||
fontResId = font, | ||
textSize = textSize, | ||
colorResId = color | ||
) | ||
|
||
private fun generateNumberBitmaps(@FontRes fontResId: Int, textSize: Float, @ColorRes colorResId: Int): Map<String, Bitmap> = (0..9).associate { number -> | ||
number.toString() to createTextBitmap( | ||
text = number.toString(), | ||
fontResId = fontResId, | ||
textSize = textSize, | ||
colorResId = colorResId | ||
) | ||
} | ||
|
||
private fun createTextBitmap(text: String, @FontRes fontResId: Int, textSize: Float, @ColorRes colorResId: Int): Bitmap { | ||
val typeface = ResourcesCompat.getFont(context, fontResId)!! | ||
val textColor = ContextCompat.getColor(context, colorResId) | ||
return textToBitmap(text, typeface, textSize, textColor) | ||
} | ||
|
||
private fun textToBitmap( | ||
text: String, | ||
typeface: Typeface, | ||
textSize: Float, | ||
textColor: Int | ||
): Bitmap { | ||
val paint = Paint().apply { | ||
this.typeface = typeface | ||
this.textSize = textSize * context.resources.displayMetrics.density | ||
this.color = textColor | ||
isAntiAlias = true | ||
textAlign = Paint.Align.LEFT | ||
} | ||
val baseline = -paint.ascent() | ||
val width = (paint.measureText(text) + 0.5f).toInt() | ||
val height = (baseline + paint.descent() + 0.5f).toInt() | ||
|
||
return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply { | ||
Canvas(this).drawText(text, 0f, baseline, paint) | ||
} | ||
} | ||
|
||
private fun combineBitmaps( | ||
digits: CharArray, | ||
isOvertime: Boolean | ||
): Bitmap { | ||
val bitmaps = mutableListOf<Bitmap>() | ||
var totalWidth = 0 | ||
var maxHeight = 0 | ||
|
||
digits.forEach { digit -> | ||
val bitmap = when (digit) { | ||
':' -> if (isOvertime) overtimeColonBitmap else colonBitmap | ||
else -> { | ||
val digitStr = digit.toString() | ||
if (isOvertime) overtimeNumberBitmaps[digitStr] else timeBitmaps[digitStr] | ||
} | ||
} | ||
bitmap?.let { | ||
bitmaps.add(it) | ||
totalWidth += it.width | ||
maxHeight = maxOf(maxHeight, it.height) | ||
} | ||
} | ||
|
||
if (isOvertime) { | ||
val extraTextBitmap = createTextBitmap( | ||
text = context.getString(R.string.timer_exceed_time), | ||
fontResId = R.font.pretendard_semibold, | ||
textSize = 18f, | ||
colorResId = R.color.notification_pomodoro_over_time | ||
) | ||
bitmaps.add(extraTextBitmap) | ||
totalWidth += extraTextBitmap.width | ||
maxHeight = maxOf(maxHeight, extraTextBitmap.height) | ||
} | ||
|
||
return Bitmap.createBitmap( | ||
totalWidth, | ||
maxHeight, | ||
Bitmap.Config.ARGB_8888 | ||
).apply { | ||
val canvas = Canvas(this) | ||
var currentX = 0f | ||
|
||
bitmaps.forEach { bitmap -> | ||
canvas.drawBitmap(bitmap, currentX, 0f, null) | ||
currentX += bitmap.width | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package com.pomonyang.mohanyang.presentation.noti | ||
|
||
import android.content.Context | ||
import android.graphics.Bitmap | ||
import android.view.View | ||
import android.widget.RemoteViews | ||
import com.mohanyang.presentation.R | ||
import com.pomonyang.mohanyang.presentation.model.setting.PomodoroCategoryType | ||
import com.pomonyang.mohanyang.presentation.util.formatTime | ||
import dagger.hilt.android.qualifiers.ApplicationContext | ||
import javax.inject.Inject | ||
|
||
internal class PomodoroNotificationContentFactory @Inject constructor( | ||
@ApplicationContext private val context: Context, | ||
private val bitmapGenerator: PomodoroNotificationBitmapGenerator | ||
) { | ||
Comment on lines
+13
to
+16
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. Service에서 Notification의 Content를 만드는 부분만 로직 분리 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. oop 맛집이네요 |
||
|
||
fun createPomodoroNotificationContent(isRest: Boolean): RemoteViews { | ||
val remoteViews = RemoteViews(context.packageName, R.layout.notification_pomodoro_standard) | ||
|
||
val titleBitmap = generateTitleBitmap(isRest) | ||
val contentBitmap = generateContentBitmap(isRest) | ||
|
||
setTitle(remoteViews, titleBitmap) | ||
setContent(remoteViews, contentBitmap) | ||
|
||
return remoteViews | ||
} | ||
|
||
fun createPomodoroNotificationBigContent( | ||
category: PomodoroCategoryType?, | ||
time: String, | ||
overtime: String? | ||
): RemoteViews { | ||
val remoteViews = RemoteViews(context.packageName, R.layout.notification_pomodoro_expand) | ||
|
||
val formattedTime = formatTimeString(time) | ||
val formattedOvertime = formatOvertimeString(overtime) | ||
|
||
val (statusBitmap, iconRes) = getStatusBitmapAndIcon(category) | ||
|
||
setStatus(remoteViews, statusBitmap) | ||
setTime(remoteViews, formattedTime) | ||
setOvertime(remoteViews, formattedOvertime) | ||
setIcon(remoteViews, iconRes) | ||
|
||
return remoteViews | ||
} | ||
|
||
private fun generateTitleBitmap(isRest: Boolean): Bitmap { | ||
val titleRes = if (isRest) R.string.notification_rest_title else R.string.notification_focus_title | ||
return bitmapGenerator.createTextBitmap( | ||
text = titleRes, | ||
color = R.color.notification_pomodoro_title, | ||
font = R.font.pretendard_semibold, | ||
textSize = 14f | ||
) | ||
} | ||
|
||
private fun generateContentBitmap(isRest: Boolean): Bitmap { | ||
val contentRes = if (isRest) R.string.notification_rest_content else R.string.notification_focus_content | ||
return bitmapGenerator.createTextBitmap( | ||
text = contentRes, | ||
color = R.color.notification_pomodoro_content, | ||
font = R.font.pretendard_regular, | ||
textSize = 14f | ||
) | ||
} | ||
|
||
private fun setTitle(remoteViews: RemoteViews, titleBitmap: Bitmap) { | ||
remoteViews.setImageViewBitmap(R.id.iv_text_title, titleBitmap) | ||
} | ||
|
||
private fun setContent(remoteViews: RemoteViews, contentBitmap: Bitmap) { | ||
remoteViews.setImageViewBitmap(R.id.iv_text_content, contentBitmap) | ||
} | ||
|
||
private fun formatTimeString(timeStr: String): String { | ||
val totalSeconds = timeStr.toIntOrNull() | ||
return totalSeconds?.formatTime() ?: context.getString(R.string.notification_timer_default_time) | ||
} | ||
|
||
private fun formatOvertimeString(overtime: String?): String? = if (!overtime.isNullOrEmpty() && overtime != "0") { | ||
formatTimeString(overtime) | ||
} else { | ||
null | ||
} | ||
|
||
private fun setStatus(remoteViews: RemoteViews, statusBitmap: Bitmap) { | ||
remoteViews.setImageViewBitmap(R.id.text_status, statusBitmap) | ||
} | ||
|
||
private fun setTime(remoteViews: RemoteViews, formattedTime: String) { | ||
val timeBitmap = bitmapGenerator.combineTimeBitmaps( | ||
time = formattedTime, | ||
isOvertime = false | ||
) | ||
remoteViews.setImageViewBitmap(R.id.text_time, timeBitmap) | ||
} | ||
|
||
private fun setOvertime(remoteViews: RemoteViews, formattedOvertime: String?) { | ||
if (formattedOvertime != null) { | ||
val overtimeBitmap = bitmapGenerator.combineTimeBitmaps( | ||
time = formattedOvertime, | ||
isOvertime = true | ||
) | ||
remoteViews.setImageViewBitmap(R.id.text_overtime, overtimeBitmap) | ||
remoteViews.setViewVisibility(R.id.text_overtime, View.VISIBLE) | ||
} else { | ||
remoteViews.setViewVisibility(R.id.text_overtime, View.GONE) | ||
} | ||
} | ||
|
||
private fun setIcon(remoteViews: RemoteViews, iconRes: Int) { | ||
remoteViews.setImageViewResource(R.id.icon_category, iconRes) | ||
} | ||
|
||
private fun getStatusBitmapAndIcon(category: PomodoroCategoryType?): Pair<Bitmap, Int> { | ||
val statusTextRes = category?.kor ?: R.string.notification_timer_rest | ||
val statusText = context.getString(statusTextRes) | ||
val statusBitmap = bitmapGenerator.createStatusBitmap(statusText) | ||
val iconRes = category?.iconRes ?: R.drawable.ic_rest | ||
return statusBitmap to iconRes | ||
} | ||
} |
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.
여러의미로 감탄을 자아내는...
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.
정말 많은 일이 있었다는 증명의 클래스야