parent
5f2ffdb306
commit
4044cee176
@ -0,0 +1,223 @@ |
|||||||
|
package net.pokeranalytics.android.ui.modules.calendar |
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel |
||||||
|
import io.realm.Realm |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
||||||
|
import net.pokeranalytics.android.model.filter.Query |
||||||
|
import net.pokeranalytics.android.model.filter.QueryCondition |
||||||
|
import net.pokeranalytics.android.model.realm.Session |
||||||
|
import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource |
||||||
|
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||||
|
import net.pokeranalytics.android.ui.view.RowViewType |
||||||
|
import net.pokeranalytics.android.util.extensions.* |
||||||
|
import java.util.* |
||||||
|
import kotlin.collections.HashMap |
||||||
|
|
||||||
|
class GridCalendarViewModel : ViewModel(), RowRepresentableDataSource { |
||||||
|
|
||||||
|
var selectedTimeUnit: TimeUnit = TimeUnit.DAY |
||||||
|
|
||||||
|
private var groups: TreeSet<Date> = TreeSet<Date>() |
||||||
|
|
||||||
|
private var results: HashMap<Date, CalendarItemCell> = HashMap() |
||||||
|
|
||||||
|
private fun addGroup(date: Date) { |
||||||
|
this.groups.add(date) |
||||||
|
} |
||||||
|
|
||||||
|
private fun result(index: Int): CalendarItemCell { |
||||||
|
val group = this.groups.elementAt(index) |
||||||
|
return this.results[group] ?: throw PAIllegalStateException("item not found") |
||||||
|
} |
||||||
|
|
||||||
|
private fun addResult(group: Date, result: CellResult, unit: TimeUnit) { |
||||||
|
this.addGroup(group) |
||||||
|
this.results[group]?.add(result) ?: run { |
||||||
|
this.results[group] = CalendarItemCell(group, result, unit) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private fun clear() { |
||||||
|
this.groups.clear() |
||||||
|
this.results.clear() |
||||||
|
} |
||||||
|
|
||||||
|
fun buildDataStructure() { |
||||||
|
|
||||||
|
// Timber.d(">>> Start buildDataStructure: ${this.selectedTimeUnit}") |
||||||
|
|
||||||
|
this.clear() |
||||||
|
|
||||||
|
val realm = Realm.getDefaultInstance() |
||||||
|
val query = Query(QueryCondition.DateNotNull, QueryCondition.EndDateNotNull) |
||||||
|
val sessions = realm.findAll<Session>(query, "startDate") |
||||||
|
|
||||||
|
val groupedSessions = when (this.selectedTimeUnit) { |
||||||
|
TimeUnit.DAY -> sessions.groupBy { it.startDate!!.startOfDay() } |
||||||
|
TimeUnit.MONTH -> sessions.groupBy { it.startDate!!.startOfMonth() } |
||||||
|
} |
||||||
|
|
||||||
|
val firstDate = sessions.firstOrNull()?.startDate?.startOf(this.selectedTimeUnit.groupingUnit) |
||||||
|
val lastDate = sessions.lastOrNull()?.startDate?.endOf(this.selectedTimeUnit.groupingUnit) |
||||||
|
|
||||||
|
// Timber.d("f = $firstDate, e = $lastDate") |
||||||
|
|
||||||
|
if (firstDate != null && lastDate != null) { |
||||||
|
var tmpDate: Date = firstDate.startOfDay() |
||||||
|
|
||||||
|
val calendar = Calendar.getInstance() |
||||||
|
while (tmpDate.time <= lastDate.time) { |
||||||
|
|
||||||
|
val result = groupedSessions[tmpDate]?.let { bucket -> |
||||||
|
if (bucket.sumByDouble { |
||||||
|
it.result?.net ?: 0.0 |
||||||
|
} > 0.0) CellResult.POSITIVE else CellResult.NEGATIVE |
||||||
|
} ?: run { |
||||||
|
CellResult.EMPTY |
||||||
|
} |
||||||
|
|
||||||
|
val groupingDate = tmpDate.startOf(this.selectedTimeUnit.groupingUnit) |
||||||
|
this.addResult(groupingDate, result, this.selectedTimeUnit) |
||||||
|
|
||||||
|
// change tmp date |
||||||
|
calendar.time = tmpDate |
||||||
|
calendar.add(selectedTimeUnit.calendarUnit, 1) |
||||||
|
tmpDate = calendar.time |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// RowRepresentableDataSource |
||||||
|
|
||||||
|
override fun adapterRows(): List<RowRepresentable> { |
||||||
|
return this.groups.descendingSet().map { this.results[it]!! } |
||||||
|
} |
||||||
|
|
||||||
|
override fun rowRepresentableForPosition(position: Int): RowRepresentable { |
||||||
|
return this.result(position) |
||||||
|
} |
||||||
|
|
||||||
|
override fun numberOfRows(): Int { |
||||||
|
return this.results.size |
||||||
|
} |
||||||
|
|
||||||
|
override fun viewTypeForPosition(position: Int): Int { |
||||||
|
return RowViewType.CALENDAR_GRID_CELL.ordinal |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
class CalendarItemCell(var date: Date, result: CellResult, var unit: TimeUnit) : RowRepresentable, RowRepresentableDataSource { |
||||||
|
|
||||||
|
private var results: MutableList<CellResult> = mutableListOf(result) |
||||||
|
private set |
||||||
|
|
||||||
|
fun add(result: CellResult) { |
||||||
|
this.results.add(result) |
||||||
|
} |
||||||
|
|
||||||
|
val size: Int = this.results.size |
||||||
|
|
||||||
|
override val viewType: Int = RowViewType.CALENDAR_GRID_CELL.ordinal |
||||||
|
|
||||||
|
val title: String |
||||||
|
get() { |
||||||
|
return when (unit) { |
||||||
|
TimeUnit.DAY -> date.getMonthAndYear() |
||||||
|
TimeUnit.MONTH -> date.getDateYear() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// RowRepresentableDataSource |
||||||
|
|
||||||
|
override fun adapterRows(): List<RowRepresentable> { |
||||||
|
return this.results |
||||||
|
} |
||||||
|
|
||||||
|
override fun rowRepresentableForPosition(position: Int): RowRepresentable { |
||||||
|
return this.results[position] |
||||||
|
} |
||||||
|
|
||||||
|
override fun numberOfRows(): Int { |
||||||
|
return this.results.size |
||||||
|
} |
||||||
|
|
||||||
|
override fun viewTypeForPosition(position: Int): Int { |
||||||
|
return RowViewType.CALENDAR_TIME_UNIT_CELL.ordinal |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
enum class TimeUnit { |
||||||
|
DAY, |
||||||
|
MONTH; |
||||||
|
|
||||||
|
val calendarUnit: Int |
||||||
|
get() { |
||||||
|
return when (this) { |
||||||
|
DAY -> Calendar.DAY_OF_MONTH |
||||||
|
MONTH -> Calendar.MONTH |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val groupingUnit: Int |
||||||
|
get() { |
||||||
|
return when (this) { |
||||||
|
DAY -> Calendar.MONTH |
||||||
|
MONTH -> Calendar.YEAR |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val spanCount: Int |
||||||
|
get() { |
||||||
|
return when (this) { |
||||||
|
DAY -> 7 |
||||||
|
MONTH -> 4 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
enum class CellResult : RowRepresentable { |
||||||
|
POSITIVE, |
||||||
|
NEGATIVE, |
||||||
|
EMPTY; |
||||||
|
|
||||||
|
val background: Int |
||||||
|
get() { |
||||||
|
return when (this) { |
||||||
|
POSITIVE -> R.drawable.rounded_green_rect |
||||||
|
NEGATIVE -> R.drawable.rounded_red_rect |
||||||
|
EMPTY -> R.drawable.rounded_grey_rect |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override val viewType: Int = RowViewType.CALENDAR_TIME_UNIT_CELL.ordinal |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private fun Date.startOf(unit: Int): Date { |
||||||
|
|
||||||
|
return when (unit) { |
||||||
|
Calendar.DAY_OF_MONTH -> this.startOfDay() |
||||||
|
Calendar.MONTH -> this.startOfMonth() |
||||||
|
Calendar.YEAR -> this.startOfYear() |
||||||
|
else -> throw PAIllegalStateException("Un-managed case: $unit") |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private fun Date.endOf(unit: Int): Date { |
||||||
|
|
||||||
|
return when (unit) { |
||||||
|
Calendar.DAY_OF_MONTH -> this.endOfDay() |
||||||
|
Calendar.MONTH -> this.endOfMonth() |
||||||
|
Calendar.YEAR -> this.endOfYear() |
||||||
|
else -> throw PAIllegalStateException("Un-managed case: $unit") |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"> |
||||||
|
<solid android:color="@color/grey"/> |
||||||
|
<!-- <stroke android:width="3dp" android:color="#B1BCBE" />--> |
||||||
|
<corners android:radius="8dp"/> |
||||||
|
</shape> |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView |
||||||
|
android:id="@+id/title" |
||||||
|
android:layout_width="0dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_marginTop="16dp" |
||||||
|
android:layout_marginBottom="16dp" |
||||||
|
style="@style/PokerAnalyticsTheme.TextView.Header" |
||||||
|
app:layout_constraintTop_toTopOf="parent" |
||||||
|
app:layout_constraintStart_toStartOf="parent" |
||||||
|
app:layout_constraintEnd_toEndOf="parent" |
||||||
|
/> |
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView |
||||||
|
android:id="@+id/recyclerView" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
app:layout_constraintTop_toBottomOf="@id/title" |
||||||
|
app:layout_constraintStart_toStartOf="parent" |
||||||
|
app:layout_constraintEnd_toEndOf="parent" |
||||||
|
app:layout_constraintBottom_toBottomOf="parent" |
||||||
|
/> |
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout> |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:id="@+id/timeUnit" |
||||||
|
android:layout_width="16dp" |
||||||
|
android:layout_height="16dp"> |
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout> |
||||||
@ -1,6 +1,77 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||||
<androidx.constraintlayout.widget.ConstraintLayout |
<androidx.constraintlayout.widget.ConstraintLayout |
||||||
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" |
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
android:layout_width="match_parent" |
||||||
android:layout_height="match_parent"> |
android:layout_height="match_parent"> |
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout |
||||||
|
android:id="@+id/appBar" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="128dp" |
||||||
|
android:theme="@style/PokerAnalyticsTheme.Toolbar.Session" |
||||||
|
app:layout_constraintEnd_toEndOf="parent" |
||||||
|
app:layout_constraintStart_toStartOf="parent" |
||||||
|
app:layout_constraintTop_toTopOf="parent"> |
||||||
|
|
||||||
|
<com.google.android.material.appbar.CollapsingToolbarLayout |
||||||
|
android:id="@+id/collapsingToolbar" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
app:collapsedTitleTextAppearance="@style/PokerAnalyticsTheme.Toolbar.CollapsedTitleAppearance" |
||||||
|
app:contentScrim="?attr/colorPrimary" |
||||||
|
app:expandedTitleGravity="bottom" |
||||||
|
app:expandedTitleMarginStart="72dp" |
||||||
|
app:expandedTitleTextAppearance="@style/PokerAnalyticsTheme.Toolbar.ExpandedTitleAppearance" |
||||||
|
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar |
||||||
|
android:id="@+id/toolbar" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="?attr/actionBarSize" |
||||||
|
app:layout_collapseMode="pin" |
||||||
|
app:title="@string/calendar" |
||||||
|
app:titleTextColor="@color/white" /> |
||||||
|
|
||||||
|
</com.google.android.material.appbar.CollapsingToolbarLayout> |
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout> |
||||||
|
|
||||||
|
<com.google.android.material.chip.ChipGroup |
||||||
|
android:id="@+id/timeUnits" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
app:layout_constraintTop_toBottomOf="@id/appBar" |
||||||
|
app:layout_constraintStart_toStartOf="parent" |
||||||
|
app:layout_constraintEnd_toEndOf="parent" |
||||||
|
app:singleSelection="true" |
||||||
|
app:selectionRequired="true" |
||||||
|
app:chipSpacing="8dp"> |
||||||
|
|
||||||
|
<com.google.android.material.chip.Chip |
||||||
|
android:id="@+id/dayUnit" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:text="@string/day" |
||||||
|
android:checked="true" /> |
||||||
|
|
||||||
|
<com.google.android.material.chip.Chip |
||||||
|
android:id="@+id/monthUnit" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:text="@string/month" /> |
||||||
|
|
||||||
|
</com.google.android.material.chip.ChipGroup> |
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView |
||||||
|
android:id="@+id/recyclerView" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="0dp" |
||||||
|
android:padding="16dp" |
||||||
|
app:layout_constraintTop_toBottomOf="@id/timeUnits" |
||||||
|
app:layout_constraintStart_toStartOf="parent" |
||||||
|
app:layout_constraintEnd_toEndOf="parent" |
||||||
|
app:layout_constraintBottom_toBottomOf="parent" |
||||||
|
/> |
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout> |
</androidx.constraintlayout.widget.ConstraintLayout> |
||||||
Loading…
Reference in new issue