Skip to content

Commit 60804d5

Browse files
committed
implement to save task
1 parent 23ab912 commit 60804d5

File tree

15 files changed

+209
-45
lines changed

15 files changed

+209
-45
lines changed

app/src/main/java/io/github/mrtry/todolist/app/todo/ui/navigator/ToDoNavigator.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,4 @@ import javax.inject.Inject
99
class ToDoNavigator
1010
@Inject constructor(
1111
private val activity: Activity
12-
) : AbsNavigator(activity) {
13-
}
12+
) : AbsNavigator(activity)

app/src/main/java/io/github/mrtry/todolist/app/todo/viewmodel/TaskViewModel.kt

+32-2
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,46 @@ package io.github.mrtry.todolist.app.todo.viewmodel
22

33
import android.view.View
44
import androidx.lifecycle.MutableLiveData
5+
import io.github.mrtry.todolist.R
6+
import io.github.mrtry.todolist.app.todo.ui.navigator.ToDoNavigator
57
import io.github.mrtry.todolist.di.scope.ActivityScope
8+
import io.github.mrtry.todolist.misc.extension.requireValue
9+
import io.github.mrtry.todolist.todo.domainservice.ToDoDomainService
10+
import io.github.mrtry.todolist.todo.entity.ToDo
11+
import kotlinx.coroutines.CancellationException
12+
import kotlinx.coroutines.CoroutineScope
13+
import kotlinx.coroutines.launch
614
import timber.log.Timber
715
import javax.inject.Inject
816

917
@ActivityScope
1018
class TaskViewModel
11-
@Inject constructor() {
19+
@Inject constructor(
20+
private val navigator: ToDoNavigator,
21+
private val domainService: ToDoDomainService,
22+
private val coroutineScope: CoroutineScope
23+
) {
1224
val taskName: MutableLiveData<String> = MutableLiveData("")
25+
val isSaving: MutableLiveData<Boolean> = MutableLiveData(false)
1326

1427
fun onAddTaskButtonClick(v: View?) {
15-
Timber.d("onClicked")
28+
coroutineScope.launch {
29+
if (taskName.requireValue().isEmpty()) return@launch
30+
31+
isSaving.value = true
32+
val todo = ToDo(title = taskName.value.orEmpty())
33+
34+
try {
35+
domainService.save(todo)
36+
taskName.value = ""
37+
} catch (e: Exception) {
38+
if (e is CancellationException) return@launch
39+
40+
Timber.e(e)
41+
navigator.showSnackBar(R.string.to_do_activity_error_failed)
42+
}
43+
44+
isSaving.value = false
45+
}
1646
}
1747
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package io.github.mrtry.todolist.app.todo.viewmodel
2+
3+
class ToDoListItemViewModel {
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package io.github.mrtry.todolist.app.todo.viewmodel.converter
2+
3+
class ToDoListItemViewModelConverter {
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.github.mrtry.todolist.misc.ui.extension
2+
3+
import androidx.databinding.ObservableList
4+
5+
fun <T> ObservableList<T>.addOnListChangedSimpleCallback(func: (items: ObservableList<T>) -> Unit) {
6+
addOnListChangedCallback(object : ObservableList.OnListChangedCallback<ObservableList<T>>() {
7+
override fun onItemRangeChanged(sender: ObservableList<T>?, positionStart: Int, itemCount: Int) {
8+
func(this@addOnListChangedSimpleCallback)
9+
}
10+
11+
override fun onItemRangeRemoved(sender: ObservableList<T>?, positionStart: Int, itemCount: Int) {
12+
func(this@addOnListChangedSimpleCallback)
13+
}
14+
15+
override fun onItemRangeMoved(sender: ObservableList<T>?, fromPosition: Int, toPosition: Int, itemCount: Int) {
16+
func(this@addOnListChangedSimpleCallback)
17+
}
18+
19+
override fun onChanged(sender: ObservableList<T>?) {
20+
func(this@addOnListChangedSimpleCallback)
21+
}
22+
23+
override fun onItemRangeInserted(sender: ObservableList<T>?, positionStart: Int, itemCount: Int) {
24+
func(this@addOnListChangedSimpleCallback)
25+
}
26+
})
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.github.mrtry.todolist.misc.extension
2+
3+
import com.google.android.gms.tasks.Task
4+
import kotlin.coroutines.resume
5+
import kotlin.coroutines.resumeWithException
6+
import kotlin.coroutines.suspendCoroutine
7+
8+
suspend fun Task<Void>.await() = suspendCoroutine<Unit> { continuation ->
9+
addOnSuccessListener { continuation.resume(Unit) }
10+
addOnFailureListener { continuation.resumeWithException(it) }
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
package io.github.mrtry.todolist.misc.ui.binding
2+

app/src/main/java/io/github/mrtry/todolist/misc/ui/navigator/AbsNavigator.kt

+1-29
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
package io.github.mrtry.todolist.misc.ui.navigator
22

33
import android.app.Activity
4-
import android.content.DialogInterface
54
import android.content.Intent
65
import android.view.View
76
import android.view.inputmethod.InputMethodManager
87
import androidx.annotation.StringRes
9-
import androidx.appcompat.app.AlertDialog
108
import androidx.databinding.ViewDataBinding
119
import com.google.android.material.snackbar.Snackbar
1210
import io.github.mrtry.todolist.misc.ui.binding.Bindable
@@ -38,36 +36,10 @@ abstract class AbsNavigator(private val activity: Activity) {
3836
activity.finish()
3937
}
4038

41-
fun showAlert(
42-
@StringRes messageId: Int,
43-
@StringRes positiveLabel: Int = android.R.string.ok,
44-
action: ((DialogInterface, Int) -> Unit)? = null
45-
) {
46-
showAlert(activity.getString(messageId), positiveLabel, action)
47-
}
48-
49-
fun showAlert(
50-
message: String,
51-
@StringRes positiveLabel: Int = android.R.string.ok,
52-
action: ((DialogInterface, Int) -> Unit)? = null,
53-
@StringRes negativeLabel: Int = android.R.string.cancel,
54-
negativeAction: ((DialogInterface, Int) -> Unit)? = null,
55-
cancelable: Boolean = true
56-
) {
57-
AlertDialog.Builder(activity)
58-
.setMessage(message)
59-
.setCancelable(cancelable)
60-
.setPositiveButton(positiveLabel, action)
61-
.setNegativeButton(negativeLabel, negativeAction)
62-
.show()
63-
}
64-
6539
open fun showSnackBar(
66-
@StringRes messageId: Int, length: Int = Snackbar.LENGTH_SHORT,
67-
@StringRes actionLabel: Int, action: (View) -> Unit
40+
@StringRes messageId: Int, length: Int = Snackbar.LENGTH_SHORT
6841
) {
6942
Snackbar.make(getBinding().root, messageId, length)
70-
.setAction(actionLabel, action)
7143
.show()
7244
}
7345

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package io.github.mrtry.todolist.todo.client
2+
3+
import com.google.firebase.Timestamp
4+
import com.google.firebase.firestore.FirebaseFirestore
5+
import io.github.mrtry.todolist.misc.extension.await
6+
import io.github.mrtry.todolist.todo.entity.ToDo
7+
import kotlinx.coroutines.Dispatchers
8+
import kotlinx.coroutines.tasks.await
9+
import kotlinx.coroutines.withContext
10+
import java.util.*
11+
import javax.inject.Inject
12+
import javax.inject.Singleton
13+
14+
private const val COLLECTION_PATH = "ToDo"
15+
16+
@Singleton
17+
class ToDoClient
18+
@Inject constructor() {
19+
private val db = FirebaseFirestore.getInstance()
20+
21+
suspend fun save(entity: ToDo) = withContext(Dispatchers.IO) {
22+
val id = entity.id ?: db.collection(COLLECTION_PATH).document().id
23+
val timestamp = entity.createdAt ?: Timestamp(Date())
24+
25+
db
26+
.collection(COLLECTION_PATH)
27+
.document(id)
28+
.set(
29+
entity.copy(
30+
id = id,
31+
createdAt = timestamp
32+
)
33+
)
34+
.await()
35+
}
36+
37+
suspend fun get(): List<ToDo> = withContext(Dispatchers.IO) {
38+
val result = db
39+
.collection(COLLECTION_PATH)
40+
.get()
41+
.await()
42+
43+
result.map {
44+
it.toObject(ToDo::class.java)
45+
}
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.github.mrtry.todolist.todo.domainservice
2+
3+
import io.github.mrtry.todolist.todo.client.ToDoClient
4+
import io.github.mrtry.todolist.todo.entity.ToDo
5+
import kotlinx.coroutines.Dispatchers
6+
import kotlinx.coroutines.withContext
7+
import javax.inject.Inject
8+
import javax.inject.Singleton
9+
10+
@Singleton
11+
class ToDoDomainService
12+
@Inject constructor(
13+
private val client: ToDoClient
14+
) {
15+
suspend fun save(entity: ToDo) = withContext(Dispatchers.IO) {
16+
client.save(entity)
17+
}
18+
19+
suspend fun get() = withContext(Dispatchers.IO) {
20+
client.get()
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.github.mrtry.todolist.todo.entity
2+
3+
import com.google.firebase.Timestamp
4+
5+
data class ToDo(
6+
val id: String? = null,
7+
var title: String = "",
8+
var description: String = "",
9+
var isComplete: Boolean = false,
10+
val createdAt: Timestamp? = null
11+
)

app/src/main/res/layout/banner_add_task.xml

+30-12
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,53 @@
44

55
<data>
66

7+
<import type="android.view.View" />
8+
79
<variable
810
name="viewModel"
911
type="io.github.mrtry.todolist.app.todo.viewmodel.TaskViewModel" />
1012
</data>
1113

1214
<androidx.constraintlayout.widget.ConstraintLayout
1315
android:layout_width="match_parent"
14-
android:layout_height="wrap_content">
16+
android:layout_height="@dimen/list_item_container_height_one_line">
1517

16-
<ImageButton
17-
android:id="@+id/add_button"
18-
android:layout_width="48dp"
19-
android:layout_height="48dp"
18+
<FrameLayout
19+
android:id="@+id/container"
20+
android:layout_width="wrap_content"
21+
android:layout_height="wrap_content"
2022
app:layout_constraintTop_toTopOf="parent"
2123
app:layout_constraintBottom_toBottomOf="parent"
22-
app:layout_constraintStart_toStartOf="parent"
23-
android:src="@drawable/ic_add"
24-
android:foreground="?attr/selectableItemBackground"
25-
android:background="@android:color/transparent"
26-
android:onClick="@{viewModel::onAddTaskButtonClick}" />
24+
app:layout_constraintStart_toStartOf="parent">
25+
26+
<ImageButton
27+
android:id="@+id/add_button"
28+
android:layout_width="@dimen/banner_add_task_button_container"
29+
android:layout_height="@dimen/banner_add_task_button_container"
30+
android:src="@drawable/ic_add"
31+
android:foreground="?attr/selectableItemBackground"
32+
android:background="@android:color/transparent"
33+
android:visibility="@{safeUnbox(viewModel.isSaving) ? View.GONE : View.VISIBLE}"
34+
android:onClick="@{viewModel::onAddTaskButtonClick}" />
35+
36+
<ProgressBar
37+
android:layout_width="@dimen/banner_add_task_progress_container"
38+
android:layout_height="@dimen/banner_add_task_progress_container"
39+
android:layout_marginVertical="@dimen/banner_add_task_progress_margin"
40+
android:layout_marginHorizontal="@dimen/banner_add_task_progress_margin"
41+
android:visibility="@{safeUnbox(viewModel.isSaving) ? View.VISIBLE : View.GONE}" />
42+
</FrameLayout>
2743

2844
<EditText
2945
android:layout_width="0dp"
3046
android:layout_height="wrap_content"
3147
app:layout_constraintTop_toTopOf="parent"
3248
app:layout_constraintBottom_toBottomOf="parent"
33-
app:layout_constraintStart_toEndOf="@+id/add_button"
49+
app:layout_constraintStart_toEndOf="@+id/container"
3450
app:layout_constraintEnd_toEndOf="parent"
35-
android:layout_marginStart="8dp"
51+
android:layout_marginStart="@dimen/banner_add_task_form_margin"
52+
android:enabled="@{!safeUnbox(viewModel.isSaving)}"
53+
android:focusable="@{!safeUnbox(viewModel.isSaving)}"
3654
android:text="@={viewModel.taskName}" />
3755
</androidx.constraintlayout.widget.ConstraintLayout>
3856
</layout>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.constraintlayout.widget.ConstraintLayout
3+
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
4+
android:layout_height="match_parent">
5+
6+
</androidx.constraintlayout.widget.ConstraintLayout>

app/src/main/res/values/dimens.xml

+10
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,14 @@
33
<dimen name="elevation_toolbar">4dp</dimen>
44

55
<dimen name="splash_icon_square_size">96dp</dimen>
6+
7+
<dimen name="banner_add_task_button_container">48dp</dimen>
8+
<dimen name="banner_add_task_progress_container">24dp</dimen>
9+
<dimen name="banner_add_task_progress_margin">12dp</dimen>
10+
<dimen name="banner_add_task_form_margin">8dp</dimen>
11+
12+
<dimen name="list_item_container_height_one_line">56dp</dimen>
13+
<dimen name="list_item_margin_horizontal">16dp</dimen>
14+
<dimen name="list_item_check_box_margin_start">8dp</dimen>
15+
<dimen name="list_item_divider_height">1dp</dimen>
616
</resources>

app/src/main/res/values/strings.xml

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
<resources>
22
<string name="app_name">ToDoList</string>
3+
<string name="to_do_activity_error_failed">登録に失敗しました。再度お試しください。</string>
34
</resources>

0 commit comments

Comments
 (0)