From 3687cce92782eecc47a4d3a0b95fefbd651ffb4c Mon Sep 17 00:00:00 2001 From: Viktor Date: Mon, 25 Mar 2019 00:34:09 +0300 Subject: [PATCH] Practice 6.1. Added Color as field of Note entity. Added Custom Color picker view. Note can be deleted. Implemented Koin. --- app/build.gradle | 14 +- app/src/main/AndroidManifest.xml | 1 + app/src/main/java/com/orionst/notist/App.kt | 7 + .../notist/common/NoteColorExtensions.kt | 29 ++++ .../orionst/notist/data/NotesRepository.kt | 6 +- .../com/orionst/notist/data/entity/Note.kt | 11 ++ .../notist/data/provider/FirestoreProvider.kt | 22 +-- .../data/provider/RemoteDataProvider.kt | 1 + .../java/com/orionst/notist/di/KoinModules.kt | 31 ++++ .../orionst/notist/ui/base/BaseFragment.kt | 4 +- .../notist/ui/custom_view/ColorCircleView.kt | 90 ++++++++++++ .../notist/ui/custom_view/ColorPickerView.kt | 104 +++++++++++++ .../notist/ui/screens/note/NoteFragment.kt | 139 ++++++++++++------ .../notist/ui/screens/note/NoteViewModel.kt | 32 ++-- .../notist/ui/screens/note/NoteViewState.kt | 4 +- .../ui/screens/note_list/LogoutDialog.kt | 2 +- .../ui/screens/note_list/NoteListFragment.kt | 24 ++- .../ui/screens/note_list/NoteListViewModel.kt | 2 +- .../note_list/NotesRecyclerViewAdapter.kt | 13 +- .../ui/screens/splash/SplashFragment.kt | 12 +- .../ui/screens/splash/SplashViewModel.kt | 2 +- .../main/res/drawable/ic_vector_delete.xml | 5 + .../main/res/drawable/ic_vector_palette.xml | 5 + app/src/main/res/layout/fragment_note.xml | 21 +++ app/src/main/res/layout/item_note.xml | 47 ++++-- app/src/main/res/menu/note_menu.xml | 13 +- app/src/main/res/values/attrs.xml | 9 ++ app/src/main/res/values/colors.xml | 11 ++ app/src/main/res/values/strings.xml | 9 +- build.gradle | 5 +- 30 files changed, 559 insertions(+), 116 deletions(-) create mode 100644 app/src/main/java/com/orionst/notist/common/NoteColorExtensions.kt create mode 100644 app/src/main/java/com/orionst/notist/di/KoinModules.kt create mode 100644 app/src/main/java/com/orionst/notist/ui/custom_view/ColorCircleView.kt create mode 100644 app/src/main/java/com/orionst/notist/ui/custom_view/ColorPickerView.kt create mode 100644 app/src/main/res/drawable/ic_vector_delete.xml create mode 100644 app/src/main/res/drawable/ic_vector_palette.xml create mode 100644 app/src/main/res/values/attrs.xml diff --git a/app/build.gradle b/app/build.gradle index 2ba5414..0b3cf3e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,14 +42,22 @@ dependencies { implementation 'com.google.android.material:material:1.1.0-alpha04' // Navigation - implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0-rc02' - implementation 'android.arch.navigation:navigation-ui-ktx:1.0.0-rc02' + implementation "android.arch.navigation:navigation-fragment-ktx:$naviagation_version" + implementation "android.arch.navigation:navigation-ui-ktx:$naviagation_version" //Firebase Firestore Service - implementation 'com.google.firebase:firebase-core:16.0.7' + implementation 'com.google.firebase:firebase-core:16.0.8' implementation 'com.google.firebase:firebase-firestore:18.1.0' implementation 'com.firebaseui:firebase-ui-auth:4.3.1' // Timber implementation 'com.github.ajalt:timberkt:1.5.1' + + // Anko + implementation "org.jetbrains.anko:anko-commons:$anko_version" + implementation "org.jetbrains.anko:anko-appcompat-v7:$anko_version" + + // Koin + implementation "org.koin:koin-android:$koin_version" + implementation "org.koin:koin-android-viewmodel:$koin_version" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 238de83..94be68b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" + android:name=".App" android:theme="@style/AppTheme.SplashTheme"> diff --git a/app/src/main/java/com/orionst/notist/App.kt b/app/src/main/java/com/orionst/notist/App.kt index 6b87e6a..de493c1 100644 --- a/app/src/main/java/com/orionst/notist/App.kt +++ b/app/src/main/java/com/orionst/notist/App.kt @@ -2,10 +2,17 @@ package com.orionst.notist import android.app.Application import com.github.ajalt.timberkt.Timber +import com.orionst.notist.di.appModule +import com.orionst.notist.di.noteListModule +import com.orionst.notist.di.noteModule +import com.orionst.notist.di.splashModule +import org.koin.android.ext.android.startKoin class App: Application() { override fun onCreate() { super.onCreate() Timber.plant(timber.log.Timber.DebugTree()) + + startKoin(this, listOf(appModule, splashModule, noteListModule, noteModule)) } } \ No newline at end of file diff --git a/app/src/main/java/com/orionst/notist/common/NoteColorExtensions.kt b/app/src/main/java/com/orionst/notist/common/NoteColorExtensions.kt new file mode 100644 index 0000000..2356e2a --- /dev/null +++ b/app/src/main/java/com/orionst/notist/common/NoteColorExtensions.kt @@ -0,0 +1,29 @@ +package com.orionst.notist.common + +import android.content.Context +import androidx.core.content.ContextCompat +import com.orionst.notist.R +import com.orionst.notist.data.entity.Note + +fun Note.Color.getColorInt(context: Context) = + ContextCompat.getColor( + context, when (this) { + Note.Color.WHITE -> R.color.white + Note.Color.YELLOW -> R.color.yellow + Note.Color.GREEN -> R.color.green + Note.Color.BLUE -> R.color.blue + Note.Color.RED -> R.color.red + Note.Color.VIOLET -> R.color.violet + Note.Color.PINK -> R.color.pink + } + ) + +fun Note.Color.getColorRes(): Int = when (this) { + Note.Color.WHITE -> R.color.white + Note.Color.VIOLET -> R.color.violet + Note.Color.YELLOW -> R.color.yellow + Note.Color.RED -> R.color.red + Note.Color.PINK -> R.color.pink + Note.Color.GREEN -> R.color.green + Note.Color.BLUE -> R.color.blue +} \ No newline at end of file diff --git a/app/src/main/java/com/orionst/notist/data/NotesRepository.kt b/app/src/main/java/com/orionst/notist/data/NotesRepository.kt index 12ea724..002d80c 100644 --- a/app/src/main/java/com/orionst/notist/data/NotesRepository.kt +++ b/app/src/main/java/com/orionst/notist/data/NotesRepository.kt @@ -1,11 +1,9 @@ package com.orionst.notist.data import com.orionst.notist.data.entity.Note -import com.orionst.notist.data.provider.FirestoreProvider import com.orionst.notist.data.provider.RemoteDataProvider -object NotesRepository { - private val remoteDataProvider: RemoteDataProvider = FirestoreProvider() +class NotesRepository(val remoteDataProvider: RemoteDataProvider) { fun getCurrentUser() = remoteDataProvider.getCurrentUser() @@ -13,5 +11,7 @@ object NotesRepository { fun saveNote(note: Note) = remoteDataProvider.saveNote(note) + fun deleteNote(id: String) = remoteDataProvider.deleteNoteById(id) + fun getNoteById(id: String) = remoteDataProvider.getNoteById(id) } \ No newline at end of file diff --git a/app/src/main/java/com/orionst/notist/data/entity/Note.kt b/app/src/main/java/com/orionst/notist/data/entity/Note.kt index 2ac933f..4beca3d 100644 --- a/app/src/main/java/com/orionst/notist/data/entity/Note.kt +++ b/app/src/main/java/com/orionst/notist/data/entity/Note.kt @@ -9,6 +9,7 @@ class Note( val id: String = "", val title: String = "", val text: String ="", + val color: Color = Color.WHITE, val lastChanged: Date = Date() ) : Parcelable { @@ -23,4 +24,14 @@ class Note( } override fun hashCode() = id.hashCode() + + enum class Color { + WHITE, + YELLOW, + GREEN, + BLUE, + RED, + VIOLET, + PINK + } } \ No newline at end of file diff --git a/app/src/main/java/com/orionst/notist/data/provider/FirestoreProvider.kt b/app/src/main/java/com/orionst/notist/data/provider/FirestoreProvider.kt index 4065fb8..8047529 100644 --- a/app/src/main/java/com/orionst/notist/data/provider/FirestoreProvider.kt +++ b/app/src/main/java/com/orionst/notist/data/provider/FirestoreProvider.kt @@ -10,17 +10,9 @@ import com.orionst.notist.data.entity.User import com.orionst.notist.data.errors.NoAuthException import com.orionst.notist.model.NoteResult -class FirestoreProvider : RemoteDataProvider { +class FirestoreProvider(private val firebaseAuth: FirebaseAuth, private val store: FirebaseFirestore) : RemoteDataProvider { - private val store by lazy { - FirebaseFirestore.getInstance() - } - - private val notesReference by lazy { - store.collection(NOTES_COLLECTION) - } - - private val currentUser get() = FirebaseAuth.getInstance().currentUser + private val currentUser get() = firebaseAuth.currentUser private fun getUserNotesCollection() = currentUser?.let { store.collection(USERS_COLLECTION).document(it.uid).collection(NOTES_COLLECTION) @@ -78,6 +70,16 @@ class FirestoreProvider : RemoteDataProvider { } } + override fun deleteNoteById(id: String) = MutableLiveData().apply { + getUserNotesCollection().document(id).delete() + .addOnSuccessListener { + value = NoteResult.Success(null) + } + .addOnFailureListener { + value = NoteResult.Error(it) + } + } + // Get notes without auth from storage // // override fun subscribeToAllNotes() = MutableLiveData().apply { diff --git a/app/src/main/java/com/orionst/notist/data/provider/RemoteDataProvider.kt b/app/src/main/java/com/orionst/notist/data/provider/RemoteDataProvider.kt index 8366683..f59d48b 100644 --- a/app/src/main/java/com/orionst/notist/data/provider/RemoteDataProvider.kt +++ b/app/src/main/java/com/orionst/notist/data/provider/RemoteDataProvider.kt @@ -9,5 +9,6 @@ interface RemoteDataProvider { fun subscribeToAllNotes(): LiveData fun getNoteById(id: String): LiveData fun saveNote(note: Note): LiveData + fun deleteNoteById(id: String): LiveData fun getCurrentUser(): LiveData } \ No newline at end of file diff --git a/app/src/main/java/com/orionst/notist/di/KoinModules.kt b/app/src/main/java/com/orionst/notist/di/KoinModules.kt new file mode 100644 index 0000000..fa4516d --- /dev/null +++ b/app/src/main/java/com/orionst/notist/di/KoinModules.kt @@ -0,0 +1,31 @@ +package com.orionst.notist.di + +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.firestore.FirebaseFirestore +import com.orionst.notist.data.NotesRepository +import com.orionst.notist.data.provider.FirestoreProvider +import com.orionst.notist.data.provider.RemoteDataProvider +import com.orionst.notist.ui.screens.note.NoteViewModel +import com.orionst.notist.ui.screens.note_list.NoteListViewModel +import com.orionst.notist.ui.screens.splash.SplashViewModel +import org.koin.android.viewmodel.ext.koin.viewModel +import org.koin.dsl.module.module + +val appModule = module { + single { FirebaseAuth.getInstance() } + single { FirebaseFirestore.getInstance() } + single { FirestoreProvider(get(), get()) } + single { NotesRepository(get()) } +} + +val splashModule = module { + viewModel { SplashViewModel(get()) } +} + +val noteListModule = module { + viewModel { NoteListViewModel(get()) } +} + +val noteModule = module { + viewModel { NoteViewModel(get()) } +} \ No newline at end of file diff --git a/app/src/main/java/com/orionst/notist/ui/base/BaseFragment.kt b/app/src/main/java/com/orionst/notist/ui/base/BaseFragment.kt index f53157a..a24bc72 100644 --- a/app/src/main/java/com/orionst/notist/ui/base/BaseFragment.kt +++ b/app/src/main/java/com/orionst/notist/ui/base/BaseFragment.kt @@ -16,14 +16,14 @@ import com.orionst.notist.data.errors.NoAuthException abstract class BaseFragment> : Fragment() { - abstract val viewModel: BaseViewModel + abstract val model: BaseViewModel companion object { private const val RC_SIGN_IN = 42001 } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - viewModel.getViewState().observe(this, Observer { viewState -> + model.getViewState().observe(this, Observer { viewState -> viewState?.apply { data?.let { renderData(it) } error?.let { renderError(it) } diff --git a/app/src/main/java/com/orionst/notist/ui/custom_view/ColorCircleView.kt b/app/src/main/java/com/orionst/notist/ui/custom_view/ColorCircleView.kt new file mode 100644 index 0000000..3a71404 --- /dev/null +++ b/app/src/main/java/com/orionst/notist/ui/custom_view/ColorCircleView.kt @@ -0,0 +1,90 @@ +package com.orionst.notist.ui.custom_view + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import androidx.annotation.ColorRes +import androidx.annotation.Dimension +import androidx.annotation.Dimension.DP +import androidx.annotation.Dimension.PX +import androidx.core.content.ContextCompat +import com.orionst.notist.R +import org.jetbrains.anko.dip + +class ColorCircleView @JvmOverloads constructor(context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) { + + companion object { + @Dimension(unit = DP) private const val defRadiusDp = 16 + @Dimension(unit = DP) private const val defStrokeWidthDp = 1 + } + + private val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + style = Paint.Style.FILL + } + + private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + style = Paint.Style.STROKE + } + + private var center: Pair = 0f to 0f + + @Dimension(unit = PX) var radius: Float = dip(defRadiusDp).toFloat() + + @ColorRes var fillColorRes: Int = R.color.white + set(value) { + field = value + fillPaint.color = ContextCompat.getColor(context, value) + } + + @ColorRes + var strokeColorRes: Int = R.color.text_secondary + set(value) { + field = value + strokePaint.color = ContextCompat.getColor(context, value) + } + + @Dimension(unit = PX) var strokeWidth: Float = dip(defStrokeWidthDp).toFloat() + set(value) { + field = value + strokePaint.strokeWidth = value + } + + init { + val a = context.obtainStyledAttributes(attrs, R.styleable.ColorCircleView) + + val defRadiusPx = dip(defRadiusDp).toFloat() + radius = a.getDimension(R.styleable.ColorCircleView_circleRadius, defRadiusPx) + fillColorRes = a.getResourceId(R.styleable.ColorCircleView_fillColor, R.color.white) + + val defStrokeWidthPx = dip(defStrokeWidthDp).toFloat() + strokeWidth = a.getDimension(R.styleable.ColorCircleView_strokeWidth, defStrokeWidthPx) + + strokeColorRes = a.getResourceId(R.styleable.ColorCircleView_strokeColor, R.color.text_secondary) + a.recycle() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val height = (radius * 2 + paddingTop + paddingBottom).toInt() + val width = (radius * 2 + paddingStart + paddingEnd).toInt() + + setMeasuredDimension(width, height) + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + center = measuredWidth/2f to measuredHeight/2f + super.onLayout(changed, left, top, right, bottom) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + canvas.drawCircle(center.first, center.second, radius, strokePaint) + canvas.drawCircle(center.first, center.second, radius - strokeWidth, fillPaint) + } + + +} diff --git a/app/src/main/java/com/orionst/notist/ui/custom_view/ColorPickerView.kt b/app/src/main/java/com/orionst/notist/ui/custom_view/ColorPickerView.kt new file mode 100644 index 0000000..24f846a --- /dev/null +++ b/app/src/main/java/com/orionst/notist/ui/custom_view/ColorPickerView.kt @@ -0,0 +1,104 @@ +package com.orionst.notist.ui.custom_view + +import android.animation.PropertyValuesHolder +import android.animation.ValueAnimator +import android.content.Context +import android.util.AttributeSet +import android.view.Gravity +import android.widget.LinearLayout +import androidx.annotation.Dimension +import androidx.annotation.Dimension.DP +import com.orionst.notist.common.getColorRes +import com.orionst.notist.data.entity.Note +import org.jetbrains.anko.dip + +class ColorPickerView : LinearLayout { + + companion object { + private const val PALETTE_ANIMATION_DURATION = 150L + private const val HEIGHT = "height" + private const val SCALE = "scale" + @Dimension(unit = DP) private const val COLOR_VIEW_PADDING = 8 + } + + var onColorClickListener: (color: Note.Color) -> Unit = { } + + val isOpen: Boolean + get() = measuredHeight > 0 + + private var desiredHeight = 0 + + private val animator by lazy { + ValueAnimator().apply { + duration = PALETTE_ANIMATION_DURATION + addUpdateListener(updateListener) + } + } + + private val updateListener by lazy { + ValueAnimator.AnimatorUpdateListener { animator -> + layoutParams.apply { + height = animator.getAnimatedValue(HEIGHT) as Int + }.let { + layoutParams = it + } + + val scaleFactor = animator.getAnimatedValue(SCALE) as Float + for (i in 0 until childCount) { + getChildAt(i).apply { + scaleX = scaleFactor + scaleY = scaleFactor + alpha = scaleFactor + } + } + } + } + + constructor(context: Context) : this(context, null, 0) + constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + orientation = HORIZONTAL + gravity = Gravity.CENTER + + Note.Color.values().forEach { color -> + addView( + ColorCircleView(context).apply { + fillColorRes = color.getColorRes() + tag = color + dip(COLOR_VIEW_PADDING).let { setPadding(it, it, it, it) } + setOnClickListener { onColorClickListener(it.tag as Note.Color) } + }) + } + + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + layoutParams.apply { + desiredHeight = height + height = 0 + }.let { + layoutParams = it + } + } + + fun open() { + animator.cancel() + animator.setValues( + PropertyValuesHolder.ofInt(HEIGHT, measuredHeight, desiredHeight), + PropertyValuesHolder.ofFloat(SCALE, getChildAt(0).scaleX, 1f) + ) + animator.start() + } + + fun close() { + animator.cancel() + animator.setValues( + PropertyValuesHolder.ofInt(HEIGHT, measuredHeight, 0), + PropertyValuesHolder.ofFloat(SCALE, getChildAt(0).scaleX, 0f) + ) + animator.start() + } + +} diff --git a/app/src/main/java/com/orionst/notist/ui/screens/note/NoteFragment.kt b/app/src/main/java/com/orionst/notist/ui/screens/note/NoteFragment.kt index 3cc59d0..cfa5260 100644 --- a/app/src/main/java/com/orionst/notist/ui/screens/note/NoteFragment.kt +++ b/app/src/main/java/com/orionst/notist/ui/screens/note/NoteFragment.kt @@ -4,28 +4,34 @@ import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup -import android.widget.EditText import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.ViewModelProviders import androidx.navigation.fragment.findNavController import com.orionst.notist.R +import com.orionst.notist.common.getColorInt import com.orionst.notist.data.entity.Note import com.orionst.notist.ui.IWidgetTuning import com.orionst.notist.ui.base.BaseFragment import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_note.* +import org.jetbrains.anko.support.v4.alert +import org.koin.android.viewmodel.ext.android.viewModel import java.util.* -class NoteFragment : BaseFragment(), IWidgetTuning { +class NoteFragment : BaseFragment(), IWidgetTuning { private var note: Note? = null private var noteId: String? = null - override val viewModel: NoteViewModel by lazy { - ViewModelProviders.of(this).get(NoteViewModel::class.java) - } + private var color = Note.Color.WHITE + + override val model: NoteViewModel by viewModel() +// override val model: NoteViewModel by lazy { +// ViewModelProviders.of(this).get(NoteViewModel::class.java) +// } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -49,37 +55,47 @@ class NoteFragment : BaseFragment(), IWidgetTuning { bottomAppBarTune() - noteId?.let { - viewModel.loadNote(it) + colorPicker.onColorClickListener = { + setViewColor(it) + color = it + saveNote() } - titleNote.afterTextChanged { saveNote() } - bodyNote.afterTextChanged { saveNote() } + noteId?.let { + model.loadNote(it) + } } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) = inflater.inflate(R.menu.note_menu, menu) + override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - android.R.id.home -> { - findNavController().popBackStack() - true - } - else -> { - super.onOptionsItemSelected(item) - } + when (item.itemId) { + android.R.id.home -> findNavController().popBackStack() + R.id.palette -> togglePalette() + R.id.delete -> deleteNote() } + return super.onOptionsItemSelected(item) } - override fun renderData(data: Note?) { - this.note = data - initUI() + override fun renderData(data: NoteViewState.Data) { + if (data.isDeleted) { + findNavController().popBackStack() + } + + this.note = data.note + initViewData() } - private fun initUI() { - note?.let { - titleNote.setText(it.title) - bodyNote.setText(it.text) - } + private fun initViewData() { + note?.run { + removeEditListener() + titleNote.setText(title) + bodyNote.setText(text) + setViewColor(color) + + setEditListener() + } } override fun bottomAppBarTune() { @@ -95,27 +111,39 @@ class NoteFragment : BaseFragment(), IWidgetTuning { } } - private fun EditText.afterTextChanged(event: (String) -> Unit) { - this.addTextChangedListener(object : TextWatcher { - private var timer = Timer() - override fun afterTextChanged(s: Editable?) { - timer.cancel() - timer = Timer() - timer.schedule(object : TimerTask() { - override fun run() { - saveNote() - } - }, SAVE_DELAY) - - saveNote() - } - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} - }) + private val textChangeWatcher = object : TextWatcher { + private var timer = Timer() + + override fun afterTextChanged(s: Editable?) { + timer.cancel() + timer = Timer() + timer.schedule(object : TimerTask() { + override fun run() { + saveNote() + } + }, SAVE_DELAY) + + saveNote() + } + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + } + + private fun setViewColor(color: Note.Color) { + color_divider.setBackgroundColor(color.getColorInt(this@NoteFragment.requireContext())) + } + + private fun togglePalette() { + if (colorPicker.isOpen) { + colorPicker.close() + } else { + colorPicker.open() + } } private fun saveNote() { + //TODO After quick click back button note can be null if (titleNote.text.isNullOrBlank() || titleNote.text!!.length < 3) return note = note?.let { @@ -123,6 +151,7 @@ class NoteFragment : BaseFragment(), IWidgetTuning { id = it.id, title = titleNote.text.toString(), text = bodyNote.text.toString(), + color = color, lastChanged = Date() ) } ?: Note( @@ -131,9 +160,29 @@ class NoteFragment : BaseFragment(), IWidgetTuning { bodyNote.text.toString() ) - note?.let { viewModel.save(note!!) } + note?.let { model.save(note!!) } + } + + private fun deleteNote() { + alert { + messageResource = R.string.delete_dialog_message + negativeButton(R.string.delete_dialog_cancel) { dialog -> dialog.dismiss() } + positiveButton(R.string.delete_dialog_submit) { model.deleteNote()} + }.show() + } + + private fun setEditListener() { + titleNote.addTextChangedListener(textChangeWatcher) + bodyNote.addTextChangedListener(textChangeWatcher) } + private fun removeEditListener() { + titleNote.removeTextChangedListener(textChangeWatcher) + bodyNote.removeTextChangedListener(textChangeWatcher) + } + + + companion object { private const val DATE_FORMAT = "dd.MM.yy HH:mm" private const val SAVE_DELAY = 500L diff --git a/app/src/main/java/com/orionst/notist/ui/screens/note/NoteViewModel.kt b/app/src/main/java/com/orionst/notist/ui/screens/note/NoteViewModel.kt index 18dbb35..62c7d40 100644 --- a/app/src/main/java/com/orionst/notist/ui/screens/note/NoteViewModel.kt +++ b/app/src/main/java/com/orionst/notist/ui/screens/note/NoteViewModel.kt @@ -5,29 +5,41 @@ import com.orionst.notist.data.entity.Note import com.orionst.notist.model.NoteResult import com.orionst.notist.ui.base.BaseViewModel -class NoteViewModel(private val repository: NotesRepository = NotesRepository) : BaseViewModel() { +class NoteViewModel(private val repository: NotesRepository) : BaseViewModel() { - private var pendingNote: Note? = null + private val currentNote: Note? + get() = viewStateLiveData.value?.data?.note fun save(note: Note){ - pendingNote = note + viewStateLiveData.value = NoteViewState(NoteViewState.Data(note = note)) } override fun onCleared() { - pendingNote?.let { + currentNote?.let { repository.saveNote(it) } } fun loadNote(noteId: String) { repository.getNoteById(noteId).observeForever { result -> - result ?: let { return@observeForever } + result?.let { + viewStateLiveData.value = when (it) { + is NoteResult.Success<*> -> NoteViewState(NoteViewState.Data(note = it.data as Note?)) + is NoteResult.Error -> NoteViewState(error = it.error) + } + } + } + } - when (result) { - is NoteResult.Success<*> -> - viewStateLiveData.value = NoteViewState(result.data as? Note) - is NoteResult.Error -> - viewStateLiveData.value = NoteViewState(error = result.error) + fun deleteNote() { + currentNote?.let {note -> + repository.deleteNote(note.id).observeForever { result-> + result?.let { + viewStateLiveData.value = when (it) { + is NoteResult.Success<*> -> NoteViewState(NoteViewState.Data(isDeleted = true)) + is NoteResult.Error -> NoteViewState(error = it.error) + } + } } } } diff --git a/app/src/main/java/com/orionst/notist/ui/screens/note/NoteViewState.kt b/app/src/main/java/com/orionst/notist/ui/screens/note/NoteViewState.kt index b5d5dd0..9049f37 100644 --- a/app/src/main/java/com/orionst/notist/ui/screens/note/NoteViewState.kt +++ b/app/src/main/java/com/orionst/notist/ui/screens/note/NoteViewState.kt @@ -3,4 +3,6 @@ package com.orionst.notist.ui.screens.note import com.orionst.notist.data.entity.Note import com.orionst.notist.ui.base.BaseViewState -class NoteViewState(note: Note? = null, error: Throwable? = null) : BaseViewState(note, error) +class NoteViewState(data: Data = Data(), error: Throwable? = null) : BaseViewState(data, error) { + data class Data(val isDeleted: Boolean = false, val note: Note? = null) +} \ No newline at end of file diff --git a/app/src/main/java/com/orionst/notist/ui/screens/note_list/LogoutDialog.kt b/app/src/main/java/com/orionst/notist/ui/screens/note_list/LogoutDialog.kt index 0877cc8..fdd3c5a 100644 --- a/app/src/main/java/com/orionst/notist/ui/screens/note_list/LogoutDialog.kt +++ b/app/src/main/java/com/orionst/notist/ui/screens/note_list/LogoutDialog.kt @@ -23,7 +23,7 @@ class LogoutDialog : DialogFragment() { AlertDialog.Builder(context) .setTitle(R.string.logout_dialog_title) .setMessage(R.string.logout_dialog_message) - .setPositiveButton(R.string.ok_btn_title) { _, _ -> listener?.onLogout() } + .setPositiveButton(R.string.logout_dialog_submit) { _, _ -> listener?.onLogout() } .setNegativeButton(R.string.logout_dialog_cancel) { _, _ -> dismiss() } .create() diff --git a/app/src/main/java/com/orionst/notist/ui/screens/note_list/NoteListFragment.kt b/app/src/main/java/com/orionst/notist/ui/screens/note_list/NoteListFragment.kt index 3709a45..91be78b 100644 --- a/app/src/main/java/com/orionst/notist/ui/screens/note_list/NoteListFragment.kt +++ b/app/src/main/java/com/orionst/notist/ui/screens/note_list/NoteListFragment.kt @@ -9,7 +9,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager -import androidx.lifecycle.ViewModelProviders import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.StaggeredGridLayoutManager @@ -22,6 +21,8 @@ import com.orionst.notist.ui.base.BaseFragment import com.orionst.notist.ui.navigation.BottomNavigationFragment import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_note_list.* +import org.jetbrains.anko.support.v4.alert +import org.koin.android.viewmodel.ext.android.viewModel class NoteListFragment : BaseFragment?, NotesViewState>(), IWidgetTuning, LogoutDialog.LogoutListener { @@ -29,9 +30,10 @@ class NoteListFragment : BaseFragment?, NotesViewState>(), IWidgetTun private lateinit var adapter: NotesRecyclerViewAdapter private val bottomNavDrawerFragment: BottomNavigationFragment = BottomNavigationFragment() - override val viewModel: NoteListViewModel by lazy { - ViewModelProviders.of(this).get(NoteListViewModel::class.java) - } + override val model: NoteListViewModel by viewModel() +// override val model: NoteListViewModel by lazy { +// ViewModelProviders.of(this).get(NoteListViewModel::class.java) +// } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) @@ -116,10 +118,16 @@ class NoteListFragment : BaseFragment?, NotesViewState>(), IWidgetTun } private fun showLogoutDialog() { - childFragmentManager.findFragmentByTag(LogoutDialog.TAG) ?: LogoutDialog.createInstance(this).show( - childFragmentManager, - LogoutDialog.TAG - ) + alert { + titleResource = R.string.logout_dialog_title + messageResource = R.string.logout_dialog_message + positiveButton(R.string.logout_dialog_submit) { onLogout() } + negativeButton(R.string.logout_dialog_cancel){ dialog -> dialog.dismiss() } + }.show() +// childFragmentManager.findFragmentByTag(LogoutDialog.TAG) ?: LogoutDialog.createInstance(this).show( +// childFragmentManager, +// LogoutDialog.TAG +// ) } } diff --git a/app/src/main/java/com/orionst/notist/ui/screens/note_list/NoteListViewModel.kt b/app/src/main/java/com/orionst/notist/ui/screens/note_list/NoteListViewModel.kt index 7680a6a..0a45ed0 100644 --- a/app/src/main/java/com/orionst/notist/ui/screens/note_list/NoteListViewModel.kt +++ b/app/src/main/java/com/orionst/notist/ui/screens/note_list/NoteListViewModel.kt @@ -6,7 +6,7 @@ import com.orionst.notist.data.entity.Note import com.orionst.notist.model.NoteResult import com.orionst.notist.ui.base.BaseViewModel -class NoteListViewModel(private val repository: NotesRepository = NotesRepository) : +class NoteListViewModel(private val repository: NotesRepository) : BaseViewModel?, NotesViewState>() { private val notesObserver = Observer { result -> diff --git a/app/src/main/java/com/orionst/notist/ui/screens/note_list/NotesRecyclerViewAdapter.kt b/app/src/main/java/com/orionst/notist/ui/screens/note_list/NotesRecyclerViewAdapter.kt index 6c551c3..1e47804 100644 --- a/app/src/main/java/com/orionst/notist/ui/screens/note_list/NotesRecyclerViewAdapter.kt +++ b/app/src/main/java/com/orionst/notist/ui/screens/note_list/NotesRecyclerViewAdapter.kt @@ -3,11 +3,13 @@ package com.orionst.notist.ui.screens.note_list import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.navigation.findNavController +import androidx.navigation.Navigation.findNavController import androidx.recyclerview.widget.RecyclerView import com.orionst.notist.R +import com.orionst.notist.common.getColorInt import com.orionst.notist.data.entity.Note -import kotlinx.android.synthetic.main.item_note.view.* +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.item_note.* class NotesRecyclerViewAdapter : RecyclerView.Adapter() { @@ -31,16 +33,17 @@ class NotesRecyclerViewAdapter : RecyclerView.Adapter() { - override val viewModel: SplashViewModel by lazy { - ViewModelProviders.of(this).get(SplashViewModel::class.java) - } + override val model: SplashViewModel by viewModel() + +// override val model: SplashViewModel by lazy { +// ViewModelProviders.of(this).get(SplashViewModel::class.java) +// } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_splash, container, false) @@ -32,7 +34,7 @@ class SplashFragment: BaseFragment() { override fun onResume() { super.onResume() Handler().postDelayed({ - viewModel.requestUser() + model.requestUser() }, START_DELAY) } diff --git a/app/src/main/java/com/orionst/notist/ui/screens/splash/SplashViewModel.kt b/app/src/main/java/com/orionst/notist/ui/screens/splash/SplashViewModel.kt index 299943e..4ecf9ed 100644 --- a/app/src/main/java/com/orionst/notist/ui/screens/splash/SplashViewModel.kt +++ b/app/src/main/java/com/orionst/notist/ui/screens/splash/SplashViewModel.kt @@ -4,7 +4,7 @@ import com.orionst.notist.data.NotesRepository import com.orionst.notist.data.errors.NoAuthException import com.orionst.notist.ui.base.BaseViewModel -class SplashViewModel(private val repository: NotesRepository = NotesRepository): BaseViewModel() { +class SplashViewModel(private val repository: NotesRepository): BaseViewModel() { fun requestUser() { repository.getCurrentUser().observeForever { diff --git a/app/src/main/res/drawable/ic_vector_delete.xml b/app/src/main/res/drawable/ic_vector_delete.xml new file mode 100644 index 0000000..8bed121 --- /dev/null +++ b/app/src/main/res/drawable/ic_vector_delete.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_vector_palette.xml b/app/src/main/res/drawable/ic_vector_palette.xml new file mode 100644 index 0000000..4abeea5 --- /dev/null +++ b/app/src/main/res/drawable/ic_vector_palette.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/fragment_note.xml b/app/src/main/res/layout/fragment_note.xml index be135b3..05a4001 100644 --- a/app/src/main/res/layout/fragment_note.xml +++ b/app/src/main/res/layout/fragment_note.xml @@ -1,6 +1,7 @@ + + + + + + + - - + android:orientation="vertical" + > + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/note_menu.xml b/app/src/main/res/menu/note_menu.xml index e73b6af..6f39759 100644 --- a/app/src/main/res/menu/note_menu.xml +++ b/app/src/main/res/menu/note_menu.xml @@ -1,4 +1,15 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..7eafcff --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1749bb6..646f00d 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -4,5 +4,16 @@ #00574B #D81B60 + #F5F5F5 + #FFF59D + #81C784 + #80d8ff + #ff8a80 + #e1bee7 + #f8bbd0 + #DFDFDF + + #66000000 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 94d0d40..0c0a003 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,9 +7,12 @@ Выйти из аккаунта? Все ваши заметки будут сохранены. Вы сможете получить к ним доступ после авторизации. Отмена - Ок + Ок - - Hello blank fragment + Сменить цвет + Удалить + Действительно удалить? + Отмена + Ок diff --git a/build.gradle b/build.gradle index d74f319..5da1019 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,9 @@ buildscript { ext.kotlin_version = '1.3.21' + ext.anko_version='0.10.8' + ext.naviagation_version='1.0.0' + ext.koin_version='1.0.1' repositories { google() jcenter() @@ -10,7 +13,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.3.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-rc02' + classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:$naviagation_version" classpath 'com.google.gms:google-services:4.2.0' } }