Skip to content

Commit

Permalink
(#2) Add attendance vote feature.
Browse files Browse the repository at this point in the history
  • Loading branch information
myung jun Hyun committed Nov 20, 2019
1 parent 636bf2d commit 59f1901
Show file tree
Hide file tree
Showing 12 changed files with 165 additions and 9 deletions.
9 changes: 7 additions & 2 deletions app/src/main/java/com/mashup/api/notice/NoticeService.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.mashup.api.notice

import com.mashup.model.Notice
import com.mashup.model.VoteStatus
import io.reactivex.Completable
import io.reactivex.Flowable
import retrofit2.http.GET
import retrofit2.http.Query
import retrofit2.http.*

interface NoticeService {

Expand All @@ -16,4 +17,8 @@ interface NoticeService {

@GET("api/notices/")
fun getNoticeList(): Flowable<List<Notice>>

@PATCH("api/notices/attendances/{id}/")
fun updateNoticeAttendance(@Path("id") userId: Int, @Body voteStatus: VoteStatus): Completable

}
8 changes: 8 additions & 0 deletions app/src/main/java/com/mashup/app/notices/NoticesFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.mashup.databinding.NoticesFragmentBinding
import com.mashup.util.EventObserver
import org.koin.androidx.viewmodel.ext.android.viewModel

class NoticesFragment : Fragment() {
Expand Down Expand Up @@ -33,6 +34,7 @@ class NoticesFragment : Fragment() {
super.onActivityCreated(savedInstanceState)
viewDataBinding.setLifecycleOwner(this.viewLifecycleOwner)
setupListAdapter()
setupObserver()
}

private fun setupListAdapter() {
Expand All @@ -43,4 +45,10 @@ class NoticesFragment : Fragment() {
}
}

private fun setupObserver() {
viewModel.itemChangedEvent.observe(this, EventObserver { position ->
listAdapter.notifyItemChanged(position)
})
}

}
42 changes: 41 additions & 1 deletion app/src/main/java/com/mashup/app/notices/NoticesViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.mashup.model.Notice
import com.mashup.model.VoteStatus
import com.mashup.repository.NoticesRepository
import com.mashup.util.Event
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
Expand All @@ -14,14 +16,18 @@ class NoticesViewModel(
) : ViewModel() {
private val _items = MutableLiveData<List<Notice>>().apply { value = emptyList() }
val items: LiveData<List<Notice>> = _items

private val _itemChangedEvent = MutableLiveData<Event<Int>>()
val itemChangedEvent: LiveData<Event<Int>> = _itemChangedEvent

private val compositeDisposable = CompositeDisposable()
private val dummyUserId = 1

init {
getNotice()
}

private fun getNotice() {
val dummyUserId = 1
compositeDisposable.add(
noticesRepository
.getNoticeList()
Expand All @@ -35,10 +41,44 @@ class NoticesViewModel(
}
}, {
it.printStackTrace()
/* TODO 에러 발생 분기 처리 */
})
)
}

fun onClickAttendButton(noticeId: Int) {
compositeDisposable.add(
noticesRepository.updateNoticeAttendance(dummyUserId, VoteStatus.ATTEND)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ updateList(noticeId, VoteStatus.ATTEND) },{ /* TODO 에러 발생 분기 처리 */ })
)
}

fun onClickAbsentButton(noticeId: Int) {
compositeDisposable.add(
noticesRepository.updateNoticeAttendance(dummyUserId, VoteStatus.ABSENT)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ updateList(noticeId, VoteStatus.ABSENT) }, { /* TODO 에러 발생 분기 처리 */ })
)
}

private fun updateList(noticeId: Int, voteStatus: VoteStatus) {
var position = 0
_items.value?.apply {
map {
position++
if (it.pk == noticeId) {
_itemChangedEvent.value = Event(position - 1)
it.userAttendance = voteStatus
} else {
it
}
}
}
}

override fun onCleared() {
compositeDisposable.clear()
super.onCleared()
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/mashup/di/NetworkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.mashup.di

import com.google.gson.GsonBuilder
import com.mashup.BuildConfig
import com.mashup.util.EnumConverterFactory
import okhttp3.Cache
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
Expand Down Expand Up @@ -36,6 +37,7 @@ val NetworkModule = module {
single {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(EnumConverterFactory())
.addConverterFactory(GsonConverterFactory.create(get()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(get())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.mashup.repository

import com.mashup.model.Notice
import com.mashup.model.VoteStatus
import com.mashup.repository.source.remote.RepositoriesRemoteDataSource
import io.reactivex.Completable
import io.reactivex.Flowable

class DefaultNoticesRepository(
private val noticesRemoteDataSource: RepositoriesRemoteDataSource
) : NoticesRepository {

private var cachedNotices = emptyList<Notice>()
private var mCacheIsDirty = false

Expand All @@ -26,4 +29,7 @@ class DefaultNoticesRepository(
.doOnComplete({ mCacheIsDirty = false })
}

}
override fun updateNoticeAttendance(userId: Int, voteStatus: VoteStatus): Completable {
return noticesRemoteDataSource.updateNoticeAttendance(userId, voteStatus)
}
}
4 changes: 4 additions & 0 deletions app/src/main/java/com/mashup/repository/NoticesRepository.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.mashup.repository

import com.mashup.model.Notice
import com.mashup.model.VoteStatus
import io.reactivex.Completable
import io.reactivex.Flowable

interface NoticesRepository {

fun getNoticeList(): Flowable<List<Notice>>

fun updateNoticeAttendance(userId: Int, voteStatus: VoteStatus): Completable
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.mashup.repository.source.remote

import com.mashup.api.notice.NoticeService
import com.mashup.model.Notice
import com.mashup.model.VoteStatus
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.schedulers.Schedulers

Expand All @@ -10,4 +12,7 @@ class RepositoriesRemoteDataSource internal constructor(
) {
fun getNoticeList(): Flowable<List<Notice>> =
noticeService.getNoticeList().subscribeOn(Schedulers.io())

fun updateNoticeAttendance(userId: Int, voteStatus: VoteStatus): Completable =
noticeService.updateNoticeAttendance(userId, voteStatus).subscribeOn(Schedulers.io())
}
27 changes: 27 additions & 0 deletions app/src/main/java/com/mashup/util/EnumConverterFactory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.mashup.util

import com.google.gson.annotations.SerializedName
import retrofit2.Converter
import retrofit2.Retrofit
import java.lang.reflect.Type

class EnumConverterFactory : Converter.Factory() {

override fun stringConverter(
type: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
): Converter<Enum<*>, String>? =
if (type is Class<*> && type.isEnum) {
Converter { enum ->
try {
enum.javaClass.getField(enum.name)
.getAnnotation(SerializedName::class.java)?.value
} catch (exception: Exception) {
null
} ?: enum.toString()
}
} else {
null
}
}
44 changes: 44 additions & 0 deletions app/src/main/java/com/mashup/util/Event.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.mashup.util

import androidx.lifecycle.Observer

/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event<out T>(private val content: T) {

@Suppress("MemberVisibilityCanBePrivate")
var hasBeenHandled = false
private set // Allow external read but not write

/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}

/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}

/**
* An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has
* already been handled.
*
* [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled.
*/
class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {
override fun onChanged(event: Event<T>?) {
event?.getContentIfNotHandled()?.let {
onEventUnhandledContent(it)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/author" />
</shape>
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/notice_attendance_button_ripple.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/author">
<item android:drawable="@android:color/white" />
</ripple>
16 changes: 11 additions & 5 deletions app/src/main/res/layout/notice_item.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
<data>

<import type="android.widget.CompoundButton" />

<import type="com.mashup.R" />

<import type="com.mashup.model.VoteStatus" />

<variable
Expand Down Expand Up @@ -83,9 +85,9 @@
android:drawableStart="@drawable/icon_date"
android:drawablePadding="4dp"
android:gravity="center_vertical"
app:noticeTime="@{notice.startAt}"
android:textColor="@color/text"
android:textSize="@dimen/text_normal"
app:noticeTime="@{notice.startAt}"
tools:text="MASH-UP" />


Expand All @@ -97,10 +99,10 @@
android:drawableStart="@drawable/icon_clock"
android:drawablePadding="4dp"
android:gravity="center_vertical"
app:startAt="@{notice.startAt}"
app:duration="@{notice.duration}"
android:textColor="@color/text"
android:textSize="@dimen/text_normal"
app:duration="@{notice.duration}"
app:startAt="@{notice.startAt}"
tools:text="MASH-UP" />

<TextView
Expand Down Expand Up @@ -169,12 +171,14 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:background="@{notice.userAttendance == VoteStatus.ATTEND ? @drawable/notice_attendance_button_background : @drawable/notice_attendance_button_ripple}"
android:clickable="@{notice.userAttendance != VoteStatus.ATTEND}"
android:drawablePadding="6dp"
android:gravity="center"
android:onClick="@{() -> viewmodel.onClickAttendButton(notice.pk)}"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:textSize="@dimen/text_small"
app:backgroundResource="@{notice.userAttendance == VoteStatus.ATTEND ? R.color.author : R.color.colorWhite}"
tools:text="MASH-UP" />

<View
Expand All @@ -187,12 +191,14 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:background="@{notice.userAttendance == VoteStatus.ABSENT ? @drawable/notice_attendance_button_background : @drawable/notice_attendance_button_ripple}"
android:clickable="@{notice.userAttendance != VoteStatus.ABSENT}"
android:drawablePadding="6dp"
android:gravity="center"
android:onClick="@{() -> viewmodel.onClickAbsentButton(notice.pk)}"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:textSize="@dimen/text_small"
app:backgroundResource="@{notice.userAttendance == VoteStatus.ABSENT ? R.color.author : R.color.colorWhite}"
tools:text="MASH-UP" />
</LinearLayout>
</LinearLayout>
Expand Down

0 comments on commit 59f1901

Please sign in to comment.