알람 기능 추가
This commit is contained in:
parent
7587b8bc25
commit
839ff7463e
|
@ -40,8 +40,8 @@ android {
|
|||
applicationId "kr.co.vividnext.sodalive"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 83
|
||||
versionName "1.13.1"
|
||||
versionCode 84
|
||||
versionName "1.13.2"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
@ -154,4 +154,9 @@ dependencies {
|
|||
|
||||
// google in-app-purchase
|
||||
implementation "com.android.billingclient:billing-ktx:6.2.0"
|
||||
|
||||
// ROOM
|
||||
kapt "androidx.room:room-compiler:2.5.0"
|
||||
implementation "androidx.room:room-ktx:2.5.0"
|
||||
implementation "androidx.room:room-runtime:2.5.0"
|
||||
}
|
||||
|
|
|
@ -39,6 +39,14 @@
|
|||
android:maxSdkVersion="32"
|
||||
tools:ignore="ScopedStorage" />
|
||||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
@ -138,6 +146,20 @@
|
|||
<activity android:name=".audio_content.series.detail.SeriesDetailActivity" />
|
||||
<activity android:name=".audio_content.series.content.SeriesContentAllActivity" />
|
||||
|
||||
<activity android:name=".mypage.alarm.AlarmListActivity" />
|
||||
<activity android:name=".mypage.alarm.AddAlarmActivity" />
|
||||
<activity android:name=".mypage.alarm.select_audio_content.AlarmSelectAudioContentActivity" />
|
||||
<activity
|
||||
android:name=".mypage.alarm.AlarmActivity"
|
||||
android:exported="true"
|
||||
android:showWhenLocked="true"
|
||||
android:turnScreenOn="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.example.alarmapp.ALARM_ACTION" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
|
||||
android:theme="@style/Theme.AppCompat.DayNight" />
|
||||
|
@ -165,6 +187,21 @@
|
|||
</service>
|
||||
<!-- [END firebase_service] -->
|
||||
|
||||
<!-- 부팅 시 알람 재설정을 위한 리시버 -->
|
||||
<receiver
|
||||
android:name=".mypage.alarm.receiver.AlarmBootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".mypage.alarm.receiver.AlarmReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- [START fcm_default_channel] -->
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||
|
|
|
@ -53,6 +53,7 @@ object Constants {
|
|||
const val EXTRA_AUDIO_CONTENT_COMMENT = "audio_content_comment"
|
||||
const val EXTRA_AUDIO_CONTENT_LOADING = "audio_content_loading"
|
||||
const val EXTRA_AUDIO_CONTENT_CREATOR_ID = "audio_content_creator_id"
|
||||
const val EXTRA_AUDIO_CONTENT_CREATOR_NICKNAME = "audio_content_creator_nickname"
|
||||
const val EXTRA_AUDIO_CONTENT_CURATION_ID = "extra_audio_content_curation_id"
|
||||
const val EXTRA_AUDIO_CONTENT_CURATION_TITLE = "extra_audio_content_curation_title"
|
||||
const val EXTRA_AUDIO_CONTENT_NEXT_ACTION = "audio_content_next_action"
|
||||
|
@ -66,4 +67,6 @@ object Constants {
|
|||
const val EXTRA_COMMUNITY_POST_ID = "community_post_id"
|
||||
const val EXTRA_COMMUNITY_CREATOR_ID = "community_creator_id"
|
||||
const val EXTRA_COMMUNITY_POST_COMMENT = "community_post_comment_id"
|
||||
|
||||
const val EXTRA_ALARM_ID = "alarm_id"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
|
||||
class Converter {
|
||||
@TypeConverter
|
||||
fun fromString(value: String): List<String> {
|
||||
val listType = object : TypeToken<List<String>>() {}.type
|
||||
return Gson().fromJson(value, listType)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromList(list: List<String>): String {
|
||||
return Gson().toJson(list)
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
|
|||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
import kr.co.vividnext.sodalive.live.reservation_status.LiveReservationStatusActivity
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.AlarmListActivity
|
||||
import kr.co.vividnext.sodalive.mypage.auth.Auth
|
||||
import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest
|
||||
import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse
|
||||
|
@ -146,6 +147,15 @@ class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflat
|
|||
)
|
||||
}
|
||||
|
||||
binding.rlAlarm.setOnClickListener {
|
||||
startActivity(
|
||||
Intent(
|
||||
requireActivity(),
|
||||
AlarmListActivity::class.java
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
binding.llReservationLive.setOnClickListener {
|
||||
startActivity(
|
||||
Intent(
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
package kr.co.vividnext.sodalive.mypage.alarm
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.CheckBox
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityAddAlarmBinding
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.db.Alarm
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.select_audio_content.AlarmSelectAudioContentActivity
|
||||
import java.util.Calendar
|
||||
|
||||
class AddAlarmActivity : BaseActivity<ActivityAddAlarmBinding>(
|
||||
ActivityAddAlarmBinding::inflate
|
||||
) {
|
||||
private val alarmViewModel: AlarmViewModel by viewModels()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var dayCheckBoxes: List<CheckBox>
|
||||
|
||||
private var alarmId: Int = 0
|
||||
private var selectedContentId: Long = 0
|
||||
private var selectedContentTitle = ""
|
||||
private var selectedContentCreatorNickname = ""
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
activityResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
this.selectedContentId = it.data?.getLongExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||
0
|
||||
) ?: 0
|
||||
|
||||
this.selectedContentTitle = it.data?.getStringExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_TITLE
|
||||
) ?: ""
|
||||
|
||||
this.selectedContentCreatorNickname = it.data?.getStringExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_CREATOR_NICKNAME
|
||||
) ?: ""
|
||||
|
||||
binding.tvContentTitle.text = selectedContentTitle
|
||||
}
|
||||
}
|
||||
|
||||
alarmId = intent.getIntExtra(Constants.EXTRA_ALARM_ID, -1)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
bindData()
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
|
||||
dayCheckBoxes = listOf(
|
||||
binding.chkSun,
|
||||
binding.chkMon,
|
||||
binding.chkTue,
|
||||
binding.chkWed,
|
||||
binding.chkThu,
|
||||
binding.chkFri,
|
||||
binding.chkSat
|
||||
)
|
||||
|
||||
if (alarmId != -1) {
|
||||
alarmViewModel.getAlarmById(alarmId).observe(this) { alarm ->
|
||||
alarm?.let {
|
||||
binding.etAlarmTitle.setText(it.title)
|
||||
|
||||
val calendar = Calendar.getInstance().apply { timeInMillis = it.time }
|
||||
binding.timePicker.hour = calendar.get(Calendar.HOUR_OF_DAY)
|
||||
binding.timePicker.minute = calendar.get(Calendar.MINUTE)
|
||||
dayCheckBoxes.forEach { checkBox ->
|
||||
checkBox.isChecked = it.days.contains(checkBox.text.toString())
|
||||
}
|
||||
binding.tvContentTitle.text = it.contentTitle
|
||||
|
||||
selectedContentId = it.contentId
|
||||
selectedContentTitle = it.contentTitle
|
||||
selectedContentCreatorNickname = it.contentCreatorNickname
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.rlSelectAlarmContent.setOnClickListener {
|
||||
activityResultLauncher.launch(
|
||||
Intent(
|
||||
applicationContext,
|
||||
AlarmSelectAudioContentActivity::class.java
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
binding.tvSave.setOnClickListener { saveAlarm() }
|
||||
|
||||
binding.toolbar.tvBack.text = "알람 설정"
|
||||
binding.toolbar.tvBack.setOnClickListener { finish() }
|
||||
binding.tvCancel.setOnClickListener { finish() }
|
||||
}
|
||||
|
||||
private fun saveAlarm() {
|
||||
if (!validate()) return
|
||||
|
||||
val hour = binding.timePicker.hour
|
||||
val minute = binding.timePicker.minute
|
||||
val alarmTitle = binding.etAlarmTitle.text.toString()
|
||||
val selectedDays = dayCheckBoxes.filter { it.isChecked }.map { it.text.toString() }
|
||||
|
||||
val alarmTime = getAdjustedTimeInMillis(hour, minute, selectedDays)
|
||||
val alarm = Alarm(
|
||||
id = if (alarmId == -1) 0 else alarmId,
|
||||
title = alarmTitle,
|
||||
time = alarmTime,
|
||||
contentId = selectedContentId,
|
||||
contentTitle = selectedContentTitle,
|
||||
contentCreatorNickname = selectedContentCreatorNickname,
|
||||
days = selectedDays.toList(),
|
||||
)
|
||||
|
||||
if (alarmId > 0) {
|
||||
alarmViewModel.update(alarm)
|
||||
} else {
|
||||
alarmViewModel.insert(alarm)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun getAdjustedTimeInMillis(hour: Int, minute: Int, selectedDays: List<String>): Long {
|
||||
val alarmTimeInMillis = getTimeInMillis(hour, minute)
|
||||
return if (selectedDays.isEmpty() && alarmTimeInMillis <= System.currentTimeMillis()) {
|
||||
getTimeInMillis(hour + 24, minute) // 다음 날로 설정
|
||||
} else {
|
||||
alarmTimeInMillis
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTimeInMillis(hour: Int, minute: Int): Long {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.set(Calendar.HOUR_OF_DAY, hour)
|
||||
calendar.set(Calendar.MINUTE, minute)
|
||||
calendar.set(Calendar.SECOND, 0)
|
||||
calendar.set(Calendar.MILLISECOND, 0)
|
||||
return calendar.timeInMillis
|
||||
}
|
||||
|
||||
private fun bindData() {
|
||||
alarmViewModel.isLoading.observe(this) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth, "")
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun validate(): Boolean {
|
||||
if (
|
||||
selectedContentId <= 0 ||
|
||||
selectedContentTitle.isBlank() ||
|
||||
selectedContentCreatorNickname.isBlank()
|
||||
) {
|
||||
Toast.makeText(applicationContext, "알람 콘텐츠를 선택하세요", Toast.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package kr.co.vividnext.sodalive.mypage.alarm
|
||||
|
||||
import android.content.Context
|
||||
import android.media.AudioManager
|
||||
import android.media.MediaPlayer
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.lifecycle.Observer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailViewModel
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityAlarmBinding
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.db.Alarm
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.db.AlarmDatabase
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class AlarmActivity : BaseActivity<ActivityAlarmBinding>(
|
||||
ActivityAlarmBinding::inflate
|
||||
) {
|
||||
|
||||
private val contentViewModel: AudioContentDetailViewModel by inject()
|
||||
|
||||
private lateinit var mediaPlayer: MediaPlayer
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
|
||||
private lateinit var audioManager: AudioManager
|
||||
private var originalVolume: Int = 0
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
setTurnScreenOn(true)
|
||||
setShowWhenLocked(true)
|
||||
} else {
|
||||
window.addFlags(
|
||||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
|
||||
or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
|
||||
or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
|
||||
)
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
initAudioManagerAndSaveOriginalVolume()
|
||||
bindData()
|
||||
getAlarm()
|
||||
}
|
||||
|
||||
private fun getAlarm() {
|
||||
val alarmId = intent.getIntExtra(Constants.EXTRA_ALARM_ID, -1)
|
||||
val alarmDao = AlarmDatabase.getDatabase(applicationContext).alarmDao()
|
||||
val alarmLiveData = alarmDao.getAlarmById(alarmId)
|
||||
|
||||
val observer = object : Observer<Alarm> {
|
||||
override fun onChanged(value: Alarm) {
|
||||
getContent(value.contentId)
|
||||
setAlarmData(value)
|
||||
alarmLiveData.removeObserver(this)
|
||||
}
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
withContext(Dispatchers.Main) {
|
||||
alarmLiveData.observeForever(observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initAudioManagerAndSaveOriginalVolume() {
|
||||
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
originalVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
|
||||
}
|
||||
|
||||
private fun setVolume(volume: Int) {
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0)
|
||||
}
|
||||
|
||||
private fun setAlarmData(alarm: Alarm) {
|
||||
binding.tvTitle.text = alarm.title
|
||||
binding.tvTime.text = SimpleDateFormat("hh:mm", Locale.getDefault())
|
||||
.format(alarm.time)
|
||||
binding.tvDate.text = SimpleDateFormat("MM월 dd일", Locale.getDefault())
|
||||
.format(alarm.time)
|
||||
}
|
||||
|
||||
private fun getContent(contentId: Long) {
|
||||
contentViewModel.getAudioContentDetail(contentId) {}
|
||||
}
|
||||
|
||||
private fun bindData() {
|
||||
contentViewModel.isLoading.observe(this) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth, "")
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
contentViewModel.audioContentLiveData.observe(this) {
|
||||
initMediaPlayer(it.contentUrl)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
binding.ivStopAlarm.setOnClickListener { finish() }
|
||||
|
||||
onBackPressedDispatcher.addCallback(
|
||||
this,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun initMediaPlayer(alarmUrl: String) {
|
||||
setVolume(audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
|
||||
mediaPlayer = MediaPlayer()
|
||||
mediaPlayer.isLooping = true
|
||||
mediaPlayer.setDataSource(alarmUrl)
|
||||
mediaPlayer.prepareAsync()
|
||||
mediaPlayer.setOnPreparedListener { mp -> mp.start() }
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
setVolume(originalVolume)
|
||||
if (mediaPlayer.isPlaying) {
|
||||
mediaPlayer.stop()
|
||||
}
|
||||
mediaPlayer.release()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package kr.co.vividnext.sodalive.mypage.alarm
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAlarmBinding
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.db.Alarm
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class AlarmAdapter(
|
||||
private val updateAlarm: (Alarm) -> Unit,
|
||||
private val deleteAlarm: (Alarm) -> Unit,
|
||||
private val onClick: (Int) -> Unit
|
||||
) : ListAdapter<Alarm, AlarmAdapter.AlarmViewHolder>(AlarmDiffCallback()) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AlarmViewHolder {
|
||||
return AlarmViewHolder(
|
||||
ItemAlarmBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: AlarmViewHolder, position: Int) {
|
||||
val alarm = getItem(position)
|
||||
holder.bind(alarm)
|
||||
}
|
||||
|
||||
inner class AlarmViewHolder(
|
||||
private val binding: ItemAlarmBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(alarm: Alarm) {
|
||||
binding.tvTitle.text = alarm.title
|
||||
binding.tvAmpm.text = SimpleDateFormat("a", Locale.getDefault())
|
||||
.format(alarm.time)
|
||||
binding.tvTime.text = SimpleDateFormat("hh:mm", Locale.getDefault())
|
||||
.format(alarm.time)
|
||||
binding.tvDays.text = if (alarm.days.isNotEmpty()) {
|
||||
alarm.getDaysText()
|
||||
} else {
|
||||
SimpleDateFormat("MM월 dd일 (E)", Locale.getDefault())
|
||||
.format(alarm.time)
|
||||
}
|
||||
|
||||
binding.tvContentTitle.text = alarm.contentTitle
|
||||
binding.tvCreatorNickname.text = alarm.contentCreatorNickname
|
||||
|
||||
binding.ivEnable.setImageResource(
|
||||
if (alarm.isEnabled) {
|
||||
R.drawable.btn_toggle_on_big
|
||||
} else {
|
||||
R.drawable.btn_toggle_off_big
|
||||
}
|
||||
)
|
||||
|
||||
binding.ivEnable.setOnClickListener {
|
||||
alarm.isEnabled = !alarm.isEnabled
|
||||
updateAlarm(alarm)
|
||||
}
|
||||
|
||||
binding.root.setOnClickListener { onClick(alarm.id) }
|
||||
binding.root.setOnLongClickListener {
|
||||
deleteAlarm(alarm)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AlarmDiffCallback : DiffUtil.ItemCallback<Alarm>() {
|
||||
override fun areItemsTheSame(oldItem: Alarm, newItem: Alarm): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Alarm, newItem: Alarm): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package kr.co.vividnext.sodalive.mypage.alarm
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.gun0912.tedpermission.PermissionListener
|
||||
import com.gun0912.tedpermission.normal.TedPermission
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.base.SodaDialog
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityAlarmListBinding
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.db.Alarm
|
||||
|
||||
class AlarmListActivity : BaseActivity<ActivityAlarmListBinding>(
|
||||
ActivityAlarmListBinding::inflate
|
||||
) {
|
||||
|
||||
private val alarmViewModel: AlarmViewModel by viewModels()
|
||||
|
||||
private lateinit var alarmAdapter: AlarmAdapter
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
checkPermissions()
|
||||
bindData()
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
|
||||
binding.tvBack.text = "알람"
|
||||
binding.tvBack.setOnClickListener { finish() }
|
||||
binding.ivPlus.setOnClickListener {
|
||||
startActivity(
|
||||
Intent(
|
||||
applicationContext,
|
||||
AddAlarmActivity::class.java
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
alarmAdapter = AlarmAdapter(
|
||||
updateAlarm = {
|
||||
alarmViewModel.update(it)
|
||||
adapterRefresh()
|
||||
},
|
||||
deleteAlarm = { showDeleteConfirm(it) },
|
||||
onClick = {
|
||||
startActivity(
|
||||
Intent(applicationContext, AddAlarmActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_ALARM_ID, it)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
binding.rvAlarm.layoutManager = LinearLayoutManager(this)
|
||||
binding.rvAlarm.adapter = alarmAdapter
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun adapterRefresh() {
|
||||
alarmViewModel.refresh()
|
||||
alarmAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun bindData() {
|
||||
alarmViewModel.allAlarms.observe(this) { alarms ->
|
||||
alarms?.let {
|
||||
alarmAdapter.submitList(it)
|
||||
binding.rvAlarm.visibility = if (it.isEmpty()) View.GONE else View.VISIBLE
|
||||
binding.tvEmptyAlarms.visibility = if (it.isEmpty()) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPermissions() {
|
||||
val permissions = mutableListOf<String>()
|
||||
|
||||
if (!Settings.canDrawOverlays(this)) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
"알람서비스를 이용하시려면 다른 앱 위에 표시 권한을 허용하셔야 합니다.",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
permissions.add(Manifest.permission.SYSTEM_ALERT_WINDOW)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.S) {
|
||||
permissions.add(Manifest.permission.SCHEDULE_EXACT_ALARM)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
permissions.add(Manifest.permission.USE_FULL_SCREEN_INTENT)
|
||||
}
|
||||
|
||||
if (permissions.isNotEmpty()) {
|
||||
TedPermission.create()
|
||||
.setPermissionListener(object : PermissionListener {
|
||||
override fun onPermissionGranted() {
|
||||
}
|
||||
|
||||
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
|
||||
finish()
|
||||
}
|
||||
})
|
||||
.setDeniedMessage(
|
||||
"권한을 거부하시면 알람서비스를 이용하실 수 없습니다."
|
||||
)
|
||||
.setPermissions(*permissions.toTypedArray())
|
||||
.check()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDeleteConfirm(alarm: Alarm) {
|
||||
SodaDialog(
|
||||
this,
|
||||
layoutInflater,
|
||||
title = "알림",
|
||||
desc = "알람을 삭제하시겠습니까?",
|
||||
confirmButtonTitle = "삭제",
|
||||
confirmButtonClick = {
|
||||
alarmViewModel.delete(alarm)
|
||||
adapterRefresh()
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
"알람이 삭제되었습니다.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
},
|
||||
cancelButtonTitle = "취소",
|
||||
).show(screenWidth)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package kr.co.vividnext.sodalive.mypage.alarm
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.db.Alarm
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.db.AlarmDao
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.db.AlarmDatabase
|
||||
|
||||
class AlarmRepository(private val alarmDao: AlarmDao) {
|
||||
val allAlarms = alarmDao.getAllAlarms()
|
||||
|
||||
suspend fun insert(alarm: Alarm) {
|
||||
withContext(Dispatchers.IO) {
|
||||
alarmDao.insertAlarm(alarm)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun update(alarm: Alarm) {
|
||||
withContext(Dispatchers.IO) {
|
||||
alarmDao.updateAlarm(alarm)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun delete(alarm: Alarm) {
|
||||
withContext(Dispatchers.IO) {
|
||||
alarmDao.deleteAlarm(alarm)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun truncate() {
|
||||
withContext(Dispatchers.IO) {
|
||||
alarmDao.truncateTable()
|
||||
}
|
||||
}
|
||||
|
||||
fun getAlarmById(id: Int): LiveData<Alarm> {
|
||||
return alarmDao.getAlarmById(id)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package kr.co.vividnext.sodalive.mypage.alarm
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.db.Alarm
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.db.AlarmDatabase
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.scheduler.AlarmScheduler
|
||||
|
||||
class AlarmViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private val repository: AlarmRepository
|
||||
private val scheduler: AlarmScheduler
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
init {
|
||||
val alarmDao = AlarmDatabase.getDatabase(application).alarmDao()
|
||||
repository = AlarmRepository(alarmDao)
|
||||
scheduler = AlarmScheduler(application)
|
||||
}
|
||||
|
||||
var allAlarms = repository.allAlarms.asLiveData()
|
||||
|
||||
fun refresh() = viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
repository.allAlarms.asLiveData()
|
||||
_isLoading.value = false
|
||||
}
|
||||
|
||||
fun insert(alarm: Alarm) = viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
|
||||
repository.insert(alarm)
|
||||
if (alarm.isEnabled) {
|
||||
scheduler.setAlarm(alarm)
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
}
|
||||
|
||||
fun update(alarm: Alarm) = viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
|
||||
repository.update(alarm)
|
||||
scheduler.cancelAlarm(alarm)
|
||||
|
||||
if (alarm.isEnabled) {
|
||||
scheduler.setAlarm(alarm)
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
}
|
||||
|
||||
fun delete(alarm: Alarm) = viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
|
||||
repository.delete(alarm)
|
||||
scheduler.cancelAlarm(alarm)
|
||||
|
||||
_isLoading.value = false
|
||||
}
|
||||
|
||||
fun truncate() = viewModelScope.launch {
|
||||
repository.truncate()
|
||||
}
|
||||
|
||||
fun getAlarmById(id: Int): LiveData<Alarm> {
|
||||
_isLoading.value = true
|
||||
|
||||
val alarm = repository.getAlarmById(id)
|
||||
|
||||
_isLoading.value = false
|
||||
|
||||
return alarm
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package kr.co.vividnext.sodalive.mypage.alarm.db
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "alarms")
|
||||
data class Alarm(
|
||||
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||
val title: String,
|
||||
val time: Long,
|
||||
val days: List<String>,
|
||||
val contentId: Long,
|
||||
val contentTitle: String,
|
||||
val contentCreatorNickname: String,
|
||||
var isEnabled: Boolean = true
|
||||
) {
|
||||
fun getDaysText(): String {
|
||||
return if (days.size == 7) "매일" else days.joinToString(", ")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package kr.co.vividnext.sodalive.mypage.alarm.db
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface AlarmDao {
|
||||
@Query("SELECT * FROM alarms WHERE id = :id")
|
||||
fun getAlarmById(id: Int): LiveData<Alarm>
|
||||
|
||||
@Query("SELECT * FROM alarms")
|
||||
fun getAllAlarms(): Flow<List<Alarm>>
|
||||
|
||||
@Query("DELETE FROM alarms")
|
||||
fun deleteAllAlarms()
|
||||
|
||||
@Query("DELETE FROM sqlite_sequence WHERE name = 'alarms'")
|
||||
fun resetAutoIncrement()
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertAlarm(alarm: Alarm)
|
||||
|
||||
@Update
|
||||
fun updateAlarm(alarm: Alarm)
|
||||
|
||||
@Delete
|
||||
fun deleteAlarm(alarm: Alarm)
|
||||
|
||||
@Transaction
|
||||
fun truncateTable() {
|
||||
deleteAllAlarms()
|
||||
resetAutoIncrement()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package kr.co.vividnext.sodalive.mypage.alarm.db
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import kr.co.vividnext.sodalive.common.Converter
|
||||
|
||||
@Database(entities = [Alarm::class], version = 1)
|
||||
@TypeConverters(Converter::class)
|
||||
abstract class AlarmDatabase : RoomDatabase() {
|
||||
abstract fun alarmDao(): AlarmDao
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: AlarmDatabase? = null
|
||||
|
||||
fun getDatabase(context: Context): AlarmDatabase {
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
val instance = Room.databaseBuilder(
|
||||
context.applicationContext,
|
||||
AlarmDatabase::class.java,
|
||||
"alarm_database"
|
||||
).build()
|
||||
INSTANCE = instance
|
||||
instance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package kr.co.vividnext.sodalive.mypage.alarm.receiver
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.db.AlarmDatabase
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.scheduler.AlarmScheduler
|
||||
|
||||
class AlarmBootReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
|
||||
val alarmDao = AlarmDatabase.getDatabase(context).alarmDao()
|
||||
val scheduler = AlarmScheduler(context)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
alarmDao.getAllAlarms().collect { alarms ->
|
||||
alarms.forEach { alarm ->
|
||||
scheduler.setAlarm(alarm)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package kr.co.vividnext.sodalive.mypage.alarm.receiver
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.lifecycle.Observer
|
||||
import com.orhanobut.logger.Logger
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.AlarmActivity
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.db.Alarm
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.db.AlarmDatabase
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.scheduler.AlarmScheduler
|
||||
|
||||
class AlarmReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
|
||||
val alarmId = intent.getIntExtra(Constants.EXTRA_ALARM_ID, -1)
|
||||
|
||||
if (alarmId > 0) {
|
||||
val alarmIntent = Intent(context, AlarmActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_ALARM_ID, alarmId)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TOP or
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
}
|
||||
|
||||
val alarmDao = AlarmDatabase.getDatabase(context).alarmDao()
|
||||
val alarmLiveData = alarmDao.getAlarmById(alarmId)
|
||||
|
||||
val observer = object : Observer<Alarm> {
|
||||
override fun onChanged(value: Alarm) {
|
||||
val scheduler = AlarmScheduler(context)
|
||||
if (value.days.isNotEmpty()) {
|
||||
scheduler.setAlarm(value)
|
||||
}
|
||||
alarmLiveData.removeObserver(this)
|
||||
}
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
withContext(Dispatchers.Main) {
|
||||
alarmLiveData.observeForever(observer)
|
||||
}
|
||||
}
|
||||
|
||||
context.startActivity(alarmIntent)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package kr.co.vividnext.sodalive.mypage.alarm.scheduler
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.db.Alarm
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.receiver.AlarmReceiver
|
||||
import java.util.Calendar
|
||||
|
||||
class AlarmScheduler(private val context: Context) {
|
||||
fun setAlarm(alarm: Alarm) {
|
||||
if (alarm.days.isEmpty()) {
|
||||
setOneTimeAlarm(alarm)
|
||||
} else {
|
||||
setRepeatingAlarm(alarm)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setOneTimeAlarm(alarm: Alarm) {
|
||||
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
|
||||
val alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
|
||||
intent.putExtra(Constants.EXTRA_ALARM_ID, alarm.id)
|
||||
PendingIntent.getBroadcast(context, alarm.id, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.timeInMillis = alarm.time
|
||||
|
||||
// 현재 시간보다 이전이면 내일 해당 시간으로 설정
|
||||
if (calendar.timeInMillis <= System.currentTimeMillis()) {
|
||||
calendar.add(Calendar.DAY_OF_YEAR, 1)
|
||||
}
|
||||
|
||||
alarmManager.setExactAndAllowWhileIdle(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
calendar.timeInMillis,
|
||||
alarmIntent
|
||||
)
|
||||
}
|
||||
|
||||
private fun setRepeatingAlarm(alarm: Alarm) {
|
||||
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
|
||||
alarm.days.forEach { day ->
|
||||
val requestCode = alarm.id * 10 + getDayOfWeek(day)
|
||||
val alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
|
||||
intent.putExtra(Constants.EXTRA_ALARM_ID, alarm.id)
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
requestCode,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
}
|
||||
|
||||
val calendar = getNextAlarmTime(day, alarm.time)
|
||||
|
||||
// 현재 시간이 설정된 알람 시간보다 늦으면 다음 주에 울리도록 설정
|
||||
if (calendar.timeInMillis <= System.currentTimeMillis()) {
|
||||
calendar.add(Calendar.WEEK_OF_YEAR, 1)
|
||||
}
|
||||
|
||||
alarmManager.setExactAndAllowWhileIdle(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
calendar.timeInMillis,
|
||||
alarmIntent
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelAlarm(alarm: Alarm) {
|
||||
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
|
||||
if (alarm.days.isEmpty()) {
|
||||
val alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
alarm.id,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
}
|
||||
alarmManager.cancel(alarmIntent)
|
||||
} else {
|
||||
alarm.days.forEach { day ->
|
||||
val requestCode = alarm.id * 10 + getDayOfWeek(day)
|
||||
val alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
requestCode,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
}
|
||||
alarmManager.cancel(alarmIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNextAlarmTime(day: String, timeInMillis: Long): Calendar {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.timeInMillis = timeInMillis
|
||||
calendar.set(Calendar.DAY_OF_WEEK, getDayOfWeek(day))
|
||||
return calendar
|
||||
}
|
||||
|
||||
private fun getDayOfWeek(day: String): Int {
|
||||
return when (day) {
|
||||
"일" -> Calendar.SUNDAY
|
||||
"월" -> Calendar.MONDAY
|
||||
"화" -> Calendar.TUESDAY
|
||||
"수" -> Calendar.WEDNESDAY
|
||||
"목" -> Calendar.THURSDAY
|
||||
"금" -> Calendar.FRIDAY
|
||||
"토" -> Calendar.SATURDAY
|
||||
else -> Calendar.MONDAY
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
package kr.co.vividnext.sodalive.mypage.alarm.select_audio_content
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderListViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.order.OrderType
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityAlarmSelectAudioContentBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AlarmSelectAudioContentActivity : BaseActivity<ActivityAlarmSelectAudioContentBinding>(
|
||||
ActivityAlarmSelectAudioContentBinding::inflate
|
||||
) {
|
||||
private val viewModel: AudioContentOrderListViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var adapter: AlarmSelectAudioContentAdapter
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
bindData()
|
||||
viewModel.getAudioContentOrderList { finish() }
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
binding.toolbar.tvBack.text = "콘텐츠 선택"
|
||||
binding.toolbar.tvBack.setOnClickListener { finish() }
|
||||
|
||||
adapter = AlarmSelectAudioContentAdapter {
|
||||
val resultIntent = Intent().apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it.contentId)
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_TITLE, it.title)
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_CREATOR_NICKNAME, it.creatorNickname)
|
||||
}
|
||||
setResult(RESULT_OK, resultIntent)
|
||||
finish()
|
||||
}
|
||||
|
||||
binding.rvOrderList.layoutManager = LinearLayoutManager(
|
||||
applicationContext,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvOrderList.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
outRect.left = 13.3f.dpToPx().toInt()
|
||||
outRect.right = 13.3f.dpToPx().toInt()
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.top = 13.3f.dpToPx().toInt()
|
||||
outRect.bottom = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
adapter.itemCount - 1 -> {
|
||||
outRect.top = 6.7f.dpToPx().toInt()
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.top = 6.7f.dpToPx().toInt()
|
||||
outRect.bottom = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvOrderList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
|
||||
val lastVisibleItemPosition = (recyclerView.layoutManager as LinearLayoutManager?)!!
|
||||
.findLastCompletelyVisibleItemPosition()
|
||||
val itemTotalCount = recyclerView.adapter!!.itemCount - 1
|
||||
|
||||
// 스크롤이 끝에 도달했는지 확인
|
||||
if (!recyclerView.canScrollVertically(1) &&
|
||||
lastVisibleItemPosition == itemTotalCount
|
||||
) {
|
||||
viewModel.getAudioContentOrderList {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvOrderList.adapter = adapter
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
viewModel.toastLiveData.observe(this) {
|
||||
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(this) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth, "")
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.orderList.observe(this) { items ->
|
||||
if (viewModel.page == 2) {
|
||||
adapter.items.clear()
|
||||
}
|
||||
|
||||
adapter.items.addAll(
|
||||
items.filter { it.orderType == OrderType.KEEP }
|
||||
)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package kr.co.vividnext.sodalive.mypage.alarm.select_audio_content
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.audio_content.order.GetAudioContentOrderListItem
|
||||
import kr.co.vividnext.sodalive.audio_content.order.OrderType
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentOrderListBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
|
||||
class AlarmSelectAudioContentAdapter(
|
||||
private val onItemClick: (GetAudioContentOrderListItem) -> Unit
|
||||
) : RecyclerView.Adapter<AlarmSelectAudioContentAdapter.ViewHolder>() {
|
||||
|
||||
var items = mutableSetOf<GetAudioContentOrderListItem>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemAudioContentOrderListBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: GetAudioContentOrderListItem) {
|
||||
binding.ivCover.load(item.coverImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_place_holder)
|
||||
transformations(RoundedCornersTransformation(5.3f.dpToPx()))
|
||||
}
|
||||
|
||||
binding.tvTitle.text = item.title
|
||||
binding.tvTheme.text = item.themeStr
|
||||
binding.tvDuration.text = item.duration
|
||||
binding.tvCreatorNickname.text = item.creatorNickname
|
||||
binding.tvLikeCount.text = item.likeCount.moneyFormat()
|
||||
binding.tvCommentCount.text = item.commentCount.moneyFormat()
|
||||
|
||||
binding.root.setOnClickListener { onItemClick(item) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemAudioContentOrderListBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items.toList()[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
}
|
|
@ -14,6 +14,7 @@ import kr.co.vividnext.sodalive.common.Constants
|
|||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.ActivitySettingsBinding
|
||||
import kr.co.vividnext.sodalive.mypage.alarm.AlarmViewModel
|
||||
import kr.co.vividnext.sodalive.settings.event.EventActivity
|
||||
import kr.co.vividnext.sodalive.settings.notice.NoticeActivity
|
||||
import kr.co.vividnext.sodalive.settings.notification.NotificationSettingsActivity
|
||||
|
@ -50,6 +51,7 @@ class SettingsActivity : BaseActivity<ActivitySettingsBinding>(ActivitySettingsB
|
|||
}
|
||||
|
||||
private val viewModel: SettingsViewModel by inject()
|
||||
private val alarmViewModel: AlarmViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
|
||||
|
@ -148,6 +150,7 @@ class SettingsActivity : BaseActivity<ActivitySettingsBinding>(ActivitySettingsB
|
|||
|
||||
viewModel.logout {
|
||||
SharedPreferenceManager.clear()
|
||||
alarmViewModel.truncate()
|
||||
finishAffinity()
|
||||
startActivity(Intent(applicationContext, SplashActivity::class.java))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@android:color/white" android:state_checked="true" />
|
||||
<item android:color="@color/color_bbbbbb" android:state_checked="false" />
|
||||
</selector>
|
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/color_3bb9f1" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_checked="false">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<corners android:radius="6.7dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_3bb9f1" />
|
||||
</shape>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="5dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_555555" />
|
||||
</shape>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/color_222222" />
|
||||
<corners
|
||||
android:topLeftRadius="13.3dp"
|
||||
android:topRightRadius="13.3dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_222222" />
|
||||
</shape>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="90"
|
||||
android:endColor="@color/color_35c2ff"
|
||||
android:startColor="@color/color_97aeff"
|
||||
android:type="linear" />
|
||||
</shape>
|
|
@ -0,0 +1,192 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/toolbar"
|
||||
layout="@layout/detail_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TimePicker
|
||||
android:id="@+id/time_picker"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="16dp"
|
||||
android:theme="@style/TimePickerStyle"
|
||||
android:timePickerMode="spinner"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/bg_top_round_corner_13_3_222222"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/time_picker">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="13.3dp"
|
||||
android:paddingVertical="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/chk_sun"
|
||||
style="@style/AlarmDayCheckBox"
|
||||
android:text="일" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/chk_mon"
|
||||
style="@style/AlarmDayCheckBox"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="월" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/chk_tue"
|
||||
style="@style/AlarmDayCheckBox"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="화" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/chk_wed"
|
||||
style="@style/AlarmDayCheckBox"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="수" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/chk_thu"
|
||||
style="@style/AlarmDayCheckBox"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="목" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/chk_fri"
|
||||
style="@style/AlarmDayCheckBox"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="금" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/chk_sat"
|
||||
style="@style/AlarmDayCheckBox"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="토" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginVertical="21dp"
|
||||
android:background="@color/color_555555" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_alarm_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_round_corner_7_transparent_555555"
|
||||
android:hint="알람 이름 입력"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:paddingHorizontal="13.3dp"
|
||||
android:paddingVertical="17dp"
|
||||
android:textColor="@color/white"
|
||||
android:textColorHint="@color/color_909090"
|
||||
android:textSize="14.7sp" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/rl_select_alarm_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="21dp"
|
||||
android:paddingVertical="13.3dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:orientation="vertical"
|
||||
tools:ignore="RelativeOverlap">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="알림음"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="14.7sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_content_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="알림음을 선택해주세요"
|
||||
android:textColor="@color/color_3bb9f1"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_forward" />
|
||||
</RelativeLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/color_555555" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="21dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_cancel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_round_corner_6_7_transparent_3bb9f1"
|
||||
android:fontFamily="@font/gmarket_sans_bold"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="취소"
|
||||
android:textColor="@color/color_3bb9f1"
|
||||
android:textSize="18.3sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_save"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="13.3dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_round_corner_6_7_3bb9f1"
|
||||
android:fontFamily="@font/gmarket_sans_bold"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="저장"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18.3sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/gradient_alarm"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="53sp"
|
||||
tools:text="6:10" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="19dp"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14.7sp"
|
||||
tools:text="7월 25일" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="19dp"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14.7sp"
|
||||
tools:text="라이브 방송 알람" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_stop_alarm"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="189dp"
|
||||
android:contentDescription="@null"
|
||||
android:gravity="center"
|
||||
android:src="@drawable/ic_alarm_stop" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="51.7dp"
|
||||
android:background="@color/black"
|
||||
android:paddingHorizontal="13.3dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_back"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:drawablePadding="6.7dp"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="@font/gmarket_sans_bold"
|
||||
android:gravity="center"
|
||||
android:minHeight="48dp"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="18.3sp"
|
||||
app:drawableStartCompat="@drawable/ic_back"
|
||||
tools:ignore="RelativeOverlap"
|
||||
tools:text="소다라이브" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_plus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_plus_no_bg" />
|
||||
</RelativeLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_alarm"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="13.3dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_empty_alarms"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="알람이 없습니다"
|
||||
android:textColor="@android:color/darker_gray"
|
||||
android:textSize="18sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/toolbar"
|
||||
layout="@layout/detail_toolbar" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_order_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingVertical="13.3dp" />
|
||||
</LinearLayout>
|
|
@ -257,6 +257,43 @@
|
|||
android:src="@drawable/ic_forward" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/rl_alarm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="13.3dp"
|
||||
android:background="@drawable/bg_round_corner_6_7_13181b"
|
||||
android:paddingHorizontal="13.3dp"
|
||||
android:paddingVertical="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_alarm"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="5.3dp"
|
||||
android:fontFamily="@font/gmarket_sans_bold"
|
||||
android:gravity="center"
|
||||
android:text="알람"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toEndOf="@+id/tv_alarm"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_alarm_clock" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_forward" />
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/black"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="13.3dp"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:textColor="@color/color_909090"
|
||||
android:textSize="12sp"
|
||||
tools:text="모닝콜" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="13.3dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_ampm"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:textColor="@color/color_d2d2d2"
|
||||
android:textSize="14sp"
|
||||
tools:text="오전" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5.3dp"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="33.3sp"
|
||||
tools:text="10:00" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_days"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:layout_toStartOf="@+id/iv_enable"
|
||||
android:layout_toEndOf="@+id/ll_time"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:gravity="end"
|
||||
android:text="월, 수, 금"
|
||||
android:textColor="@color/color_909090"
|
||||
android:textSize="11sp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_enable"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/btn_toggle_on_big" />
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="13.3dp"
|
||||
android:background="@drawable/bg_round_corner_5_3_222222"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="13.3dp"
|
||||
android:paddingVertical="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_creator_nickname"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:textColor="@color/color_909090"
|
||||
android:textSize="12sp"
|
||||
tools:text="설린" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_content_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16.7dp"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:maxLines="2"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="14.7sp"
|
||||
tools:text="[야함끝판왕/리얼플🔞] 강제로 절정까지 가버리는 연상녀💦" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/color_555555" />
|
||||
</LinearLayout>
|
|
@ -34,6 +34,8 @@
|
|||
<color name="color_3d2a6c">#3D2A6C</color>
|
||||
<color name="color_ffdc00">#FFDC00</color>
|
||||
<color name="color_4999e3">#4999E3</color>
|
||||
<color name="color_35c2ff">#35C2FF</color>
|
||||
<color name="color_97aeff">#97AEFF</color>
|
||||
<color name="color_ccc25264">#CCC25264</color>
|
||||
|
||||
<color name="color_b3909090">#B3909090</color>
|
||||
|
|
|
@ -59,4 +59,16 @@
|
|||
<style name="AppModalStyle" parent="Widget.Design.BottomSheet.Modal">
|
||||
<item name="android:background">@drawable/bg_top_round_corner_16_7_222222</item>
|
||||
</style>
|
||||
|
||||
<style name="AlarmDayCheckBox">
|
||||
<item name="android:button">@null</item>
|
||||
<item name="android:background">@drawable/alarm_day_checkbox_selector</item>
|
||||
<item name="android:layout_width">0dp</item>
|
||||
<item name="android:layout_height">40dp</item>
|
||||
<item name="android:layout_weight">1</item>
|
||||
<item name="android:padding">8dp</item>
|
||||
<item name="android:textSize">15sp</item>
|
||||
<item name="android:gravity">center</item>
|
||||
<item name="android:textColor">@color/alarm_day_checkbox_text_selector</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue