Very Very barebones wearos skeleton

This commit is contained in:
Phillip Thelen 2022-06-03 11:23:14 +02:00
parent 6c536fa655
commit 4f4ac804b5
85 changed files with 559 additions and 158 deletions

View file

@ -129,10 +129,6 @@
<string name="usernames_party">%s\'s Party</string>
<string name="chat">Chat</string>
<string name="members">Members</string>
<string name="habits">Habits</string>
<string name="dailies">Dailies</string>
<string name="todos">To Do\'s</string>
<string name="rewards">Rewards</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="discard">Discard</string>
@ -603,7 +599,6 @@
<string name="come_back_soon">Come back soon!</string>
<string name="level">Level</string>
<string name="streak_label">21-Day Streaks</string>
<string name="stats">Stats</string>
<string name="fix_character_description">If youve encountered a bug or made a mistake that unfairly changed your character, you can manually correct those values here.</string>
<string name="fix_character_values">Fix Character Values</string>
<string name="saving">Saving</string>

View file

Before

Width:  |  Height:  |  Size: 400 B

After

Width:  |  Height:  |  Size: 400 B

View file

Before

Width:  |  Height:  |  Size: 412 B

After

Width:  |  Height:  |  Size: 412 B

View file

Before

Width:  |  Height:  |  Size: 580 B

After

Width:  |  Height:  |  Size: 580 B

View file

Before

Width:  |  Height:  |  Size: 512 B

After

Width:  |  Height:  |  Size: 512 B

View file

Before

Width:  |  Height:  |  Size: 266 B

After

Width:  |  Height:  |  Size: 266 B

View file

Before

Width:  |  Height:  |  Size: 236 B

After

Width:  |  Height:  |  Size: 236 B

View file

Before

Width:  |  Height:  |  Size: 280 B

After

Width:  |  Height:  |  Size: 280 B

View file

Before

Width:  |  Height:  |  Size: 584 B

After

Width:  |  Height:  |  Size: 584 B

View file

Before

Width:  |  Height:  |  Size: 330 B

After

Width:  |  Height:  |  Size: 330 B

View file

Before

Width:  |  Height:  |  Size: 348 B

After

Width:  |  Height:  |  Size: 348 B

View file

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 450 B

View file

Before

Width:  |  Height:  |  Size: 420 B

After

Width:  |  Height:  |  Size: 420 B

View file

Before

Width:  |  Height:  |  Size: 164 B

After

Width:  |  Height:  |  Size: 164 B

View file

Before

Width:  |  Height:  |  Size: 191 B

After

Width:  |  Height:  |  Size: 191 B

View file

Before

Width:  |  Height:  |  Size: 134 B

After

Width:  |  Height:  |  Size: 134 B

View file

Before

Width:  |  Height:  |  Size: 171 B

After

Width:  |  Height:  |  Size: 171 B

View file

Before

Width:  |  Height:  |  Size: 182 B

After

Width:  |  Height:  |  Size: 182 B

View file

Before

Width:  |  Height:  |  Size: 216 B

After

Width:  |  Height:  |  Size: 216 B

View file

Before

Width:  |  Height:  |  Size: 382 B

After

Width:  |  Height:  |  Size: 382 B

View file

Before

Width:  |  Height:  |  Size: 925 B

After

Width:  |  Height:  |  Size: 925 B

View file

Before

Width:  |  Height:  |  Size: 466 B

After

Width:  |  Height:  |  Size: 466 B

View file

Before

Width:  |  Height:  |  Size: 474 B

After

Width:  |  Height:  |  Size: 474 B

View file

Before

Width:  |  Height:  |  Size: 676 B

After

Width:  |  Height:  |  Size: 676 B

View file

Before

Width:  |  Height:  |  Size: 626 B

After

Width:  |  Height:  |  Size: 626 B

View file

Before

Width:  |  Height:  |  Size: 238 B

After

Width:  |  Height:  |  Size: 238 B

View file

Before

Width:  |  Height:  |  Size: 274 B

After

Width:  |  Height:  |  Size: 274 B

View file

Before

Width:  |  Height:  |  Size: 226 B

After

Width:  |  Height:  |  Size: 226 B

View file

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 282 B

View file

Before

Width:  |  Height:  |  Size: 304 B

After

Width:  |  Height:  |  Size: 304 B

View file

Before

Width:  |  Height:  |  Size: 402 B

After

Width:  |  Height:  |  Size: 402 B

View file

Before

Width:  |  Height:  |  Size: 748 B

After

Width:  |  Height:  |  Size: 748 B

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

Before

Width:  |  Height:  |  Size: 560 B

After

Width:  |  Height:  |  Size: 560 B

View file

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 552 B

View file

Before

Width:  |  Height:  |  Size: 848 B

After

Width:  |  Height:  |  Size: 848 B

View file

Before

Width:  |  Height:  |  Size: 814 B

After

Width:  |  Height:  |  Size: 814 B

View file

Before

Width:  |  Height:  |  Size: 328 B

After

Width:  |  Height:  |  Size: 328 B

View file

Before

Width:  |  Height:  |  Size: 368 B

After

Width:  |  Height:  |  Size: 368 B

View file

Before

Width:  |  Height:  |  Size: 312 B

After

Width:  |  Height:  |  Size: 312 B

View file

Before

Width:  |  Height:  |  Size: 372 B

After

Width:  |  Height:  |  Size: 372 B

View file

Before

Width:  |  Height:  |  Size: 420 B

After

Width:  |  Height:  |  Size: 420 B

View file

Before

Width:  |  Height:  |  Size: 559 B

After

Width:  |  Height:  |  Size: 559 B

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -15,4 +15,10 @@
<string name="easy">Easy</string>
<string name="medium">Medium</string>
<string name="hard">Hard</string>
<string name="habits">Habits</string>
<string name="dailies">Dailies</string>
<string name="todos">To Do\'s</string>
<string name="rewards">Rewards</string>
<string name="stats">Stats</string>
</resources>

View file

@ -30,14 +30,13 @@ android {
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'com.google.android.gms:play-services-wearable:17.1.0'
implementation 'androidx.percentlayout:percentlayout:1.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.wear:wear:1.2.0'
implementation "androidx.wear:wear-input:1.1.0"
//Networking
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'
@ -47,6 +46,7 @@ dependencies {
}
implementation('com.squareup.retrofit2:converter-moshi:2.9.0')
implementation("com.squareup.moshi:moshi-kotlin:1.13.0")
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.13.0")
//Analytics
implementation 'com.amplitude:android-sdk:3.35.1'
@ -72,4 +72,22 @@ dependencies {
}
repositories {
mavenCentral()
}
final File HRPG_PROPS_FILE = new File(projectDir.absolutePath + '/../habitica.properties')
if (HRPG_PROPS_FILE.canRead()) {
Properties HRPG_PROPS = new Properties()
HRPG_PROPS.load(new FileInputStream(HRPG_PROPS_FILE))
if (HRPG_PROPS != null) {
android.buildTypes.all { buildType ->
HRPG_PROPS.any { property ->
buildType.buildConfigField "String", property.key, "\"${property.value}\""
}
}
} else {
throw new MissingResourceException('habitica.properties found but some entries are missing')
}
} else {
throw new MissingResourceException('habitica.properties not found')
}

View file

@ -35,6 +35,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ui.activities.TaskListActivity" />
</application>
</manifest>

View file

@ -96,4 +96,6 @@ class ApiClient @Inject constructor(
suspend fun removePushDevice(id: String) = apiService.removePushDevice(id).data
suspend fun runCron() = apiService.runCron().data
suspend fun getTasks() = apiService.getTasks().data
}

View file

@ -3,9 +3,9 @@ package com.habitrpg.wearos.habitica.data
import com.habitrpg.common.habitica.models.auth.UserAuth
import com.habitrpg.common.habitica.models.auth.UserAuthResponse
import com.habitrpg.common.habitica.models.auth.UserAuthSocial
import com.habitrpg.common.habitica.models.responses.HabitResponse
import com.habitrpg.common.habitica.models.responses.TaskDirectionData
import com.habitrpg.wearos.habitica.models.User
import com.habitrpg.wearos.habitica.models.WearableHabitResponse
import com.habitrpg.wearos.habitica.models.tasks.BulkTaskScoringData
import com.habitrpg.wearos.habitica.models.tasks.Task
import com.habitrpg.wearos.habitica.models.tasks.TaskList
@ -20,74 +20,77 @@ import retrofit2.http.Query
interface ApiService {
@GET("user/")
suspend fun getUser(): HabitResponse<User>
suspend fun getUser(): WearableHabitResponse<User>
@PUT("user/")
suspend fun updateUser(@Body updateDictionary: Map<String, Any>): HabitResponse<User>
suspend fun updateUser(@Body updateDictionary: Map<String, Any>): WearableHabitResponse<User>
@PUT("user/")
suspend fun registrationLanguage(@Header("Accept-Language") registrationLanguage: String): HabitResponse<User>
suspend fun registrationLanguage(@Header("Accept-Language") registrationLanguage: String): WearableHabitResponse<User>
@GET("tasks/user")
suspend fun getTasks(@Query("type") type: String): HabitResponse<TaskList>
suspend fun getTasks(): WearableHabitResponse<TaskList>
@GET("tasks/user")
suspend fun getTasks(@Query("type") type: String, @Query("dueDate") dueDate: String): HabitResponse<TaskList>
suspend fun getTasks(@Query("type") type: String): WearableHabitResponse<TaskList>
@GET("tasks/user")
suspend fun getTasks(@Query("type") type: String, @Query("dueDate") dueDate: String): WearableHabitResponse<TaskList>
@GET("tasks/{id}")
suspend fun getTask(@Path("id") id: String): HabitResponse<Task>
suspend fun getTask(@Path("id") id: String): WearableHabitResponse<Task>
@POST("tasks/{id}/score/{direction}")
suspend fun postTaskDirection(@Path("id") id: String, @Path("direction") direction: String): HabitResponse<TaskDirectionData>
suspend fun postTaskDirection(@Path("id") id: String, @Path("direction") direction: String): WearableHabitResponse<TaskDirectionData>
@POST("tasks/bulk-score")
suspend fun bulkScoreTasks(@Body data: List<Map<String, String>>): HabitResponse<BulkTaskScoringData>
suspend fun bulkScoreTasks(@Body data: List<Map<String, String>>): WearableHabitResponse<BulkTaskScoringData>
@POST("tasks/{id}/move/to/{position}")
suspend fun postTaskNewPosition(@Path("id") id: String, @Path("position") position: Int): HabitResponse<List<String>>
suspend fun postTaskNewPosition(@Path("id") id: String, @Path("position") position: Int): WearableHabitResponse<List<String>>
@POST("tasks/{taskId}/checklist/{itemId}/score")
suspend fun scoreChecklistItem(@Path("taskId") taskId: String, @Path("itemId") itemId: String): HabitResponse<Task>
suspend fun scoreChecklistItem(@Path("taskId") taskId: String, @Path("itemId") itemId: String): WearableHabitResponse<Task>
@POST("tasks/user")
suspend fun createTask(@Body item: Task): HabitResponse<Task>
suspend fun createTask(@Body item: Task): WearableHabitResponse<Task>
@POST("tasks/user")
suspend fun createTasks(@Body tasks: List<Task>): HabitResponse<List<Task>>
suspend fun createTasks(@Body tasks: List<Task>): WearableHabitResponse<List<Task>>
@PUT("tasks/{id}")
suspend fun updateTask(@Path("id") id: String, @Body item: Task): HabitResponse<Task>
suspend fun updateTask(@Path("id") id: String, @Body item: Task): WearableHabitResponse<Task>
@DELETE("tasks/{id}")
suspend fun deleteTask(@Path("id") id: String): HabitResponse<Void>
suspend fun deleteTask(@Path("id") id: String): WearableHabitResponse<Void>
@POST("user/auth/local/register")
suspend fun registerUser(@Body auth: UserAuth): HabitResponse<UserAuthResponse>
suspend fun registerUser(@Body auth: UserAuth): WearableHabitResponse<UserAuthResponse>
@POST("user/auth/local/login")
suspend fun connectLocal(@Body auth: UserAuth): HabitResponse<UserAuthResponse>
suspend fun connectLocal(@Body auth: UserAuth): WearableHabitResponse<UserAuthResponse>
@POST("user/auth/social")
suspend fun connectSocial(@Body auth: UserAuthSocial): HabitResponse<UserAuthResponse>
suspend fun connectSocial(@Body auth: UserAuthSocial): WearableHabitResponse<UserAuthResponse>
@DELETE("user/auth/social/{network}")
suspend fun disconnectSocial(@Path("network") network: String): HabitResponse<Void>
suspend fun disconnectSocial(@Path("network") network: String): WearableHabitResponse<Void>
@POST("user/auth/apple")
suspend fun loginApple(@Body auth: Map<String, Any>): HabitResponse<UserAuthResponse>
suspend fun loginApple(@Body auth: Map<String, Any>): WearableHabitResponse<UserAuthResponse>
@POST("user/sleep")
suspend fun sleep(): HabitResponse<Boolean>
suspend fun sleep(): WearableHabitResponse<Boolean>
@POST("user/revive")
suspend fun revive(): HabitResponse<User>
suspend fun revive(): WearableHabitResponse<User>
// Push notifications
@POST("user/push-devices")
suspend fun addPushDevice(@Body pushDeviceData: Map<String, String>): HabitResponse<List<Void>>
suspend fun addPushDevice(@Body pushDeviceData: Map<String, String>): WearableHabitResponse<List<Void>>
@DELETE("user/push-devices/{regId}")
suspend fun removePushDevice(@Path("regId") regId: String): HabitResponse<List<Void>>
suspend fun removePushDevice(@Path("regId") regId: String): WearableHabitResponse<List<Void>>
@POST("cron")
suspend fun runCron(): HabitResponse<Void>
suspend fun runCron(): WearableHabitResponse<Void>
}

View file

@ -0,0 +1,80 @@
package com.habitrpg.wearos.habitica.data
import android.os.Build
import com.habitrpg.common.habitica.models.tasks.Attribute
import com.habitrpg.common.habitica.models.tasks.Frequency
import com.habitrpg.common.habitica.models.tasks.TaskType
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import java.text.DateFormat
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
var customDateAdapter: Any = object : Any() {
@ToJson
@Synchronized
fun dateToJson(d: Date?): String? {
return d?.let { dateFormats[0].format(it) }
}
@FromJson
@Synchronized
@Throws(ParseException::class)
fun dateFromJson(s: String?): Date? {
var date: Date? = null
var index = 0
while (index < dateFormats.size && date == null) {
try {
date = dateFormats[index].parse(s)
} catch (_: ParseException) {}
index += 1
}
return date
}
private var dateFormats = mutableListOf<DateFormat>()
init {
addFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
addFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
addFormat("E MMM dd yyyy HH:mm:ss zzzz")
addFormat("yyyy-MM-dd'T'HH:mm:sszzz")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
addFormat("yyyy-MM-dd'T'HH:mmX")
} else {
addFormat("yyyy-MM-dd'T'HH:mm")
}
addFormat("yyyy-MM-dd")
}
private fun addFormat(s: String) {
val dateFormat = SimpleDateFormat(s, Locale.US)
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
dateFormats.add(dateFormat)
}
}
class FrequencyAdapter {
@ToJson
fun toJson(type: Frequency): String = type.value
@FromJson
fun fromJson(value: String): Frequency? = Frequency.from(value)
}
class TaskTypeAdapter {
@ToJson
fun toJson(type: TaskType): String = type.value
@FromJson
fun fromJson(value: String): TaskType? = TaskType.from(value)
}
class AttributeAdapter {
@ToJson
fun toJson(type: Attribute): String = type.value
@FromJson
fun fromJson(value: String): Attribute? = Attribute.from(value)
}

View file

@ -4,4 +4,7 @@ import com.habitrpg.wearos.habitica.data.ApiClient
import javax.inject.Inject
class TaskRepository @Inject constructor(val apiClient: ApiClient, val localRepository: TaskLocalRepository) {
suspend fun retrieveTasks() = apiClient.getTasks()
}

View file

@ -1,7 +1,9 @@
package com.habitrpg.wearos.habitica.models
import com.habitrpg.common.habitica.models.AvatarBuffs
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class Buffs: AvatarBuffs {
override var con: Float? = null
override var str: Float? = null

View file

@ -1,7 +1,9 @@
package com.habitrpg.wearos.habitica.models
import com.habitrpg.common.habitica.models.AvatarFlags
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class Flags: AvatarFlags {
override var classSelected: Boolean = false
}

View file

@ -1,7 +1,9 @@
package com.habitrpg.wearos.habitica.models
import com.habitrpg.common.habitica.models.AvatarHair
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class Hair: AvatarHair {
override var mustache: Int = 0
override var beard: Int = 0

View file

@ -1,7 +1,9 @@
package com.habitrpg.wearos.habitica.models
import com.habitrpg.common.habitica.models.AvatarOutfit
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class Outfit: AvatarOutfit {
override var armor: String = ""
override var back: String = ""

View file

@ -1,8 +1,9 @@
package com.habitrpg.wearos.habitica.models
import com.habitrpg.common.habitica.models.AvatarHair
import com.habitrpg.common.habitica.models.AvatarPreferences
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class Preferences: AvatarPreferences {
override val hair: Hair? = null
override val costume: Boolean = false

View file

@ -1,7 +1,9 @@
package com.habitrpg.wearos.habitica.models
import com.habitrpg.common.habitica.models.AvatarStats
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class Stats: AvatarStats {
override val buffs: Buffs? = null
override var habitClass: String? = null

View file

@ -1,16 +1,20 @@
package com.habitrpg.wearos.habitica.models
import com.habitrpg.common.habitica.models.Avatar
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
open class Gear {
var equipped: Outfit? = null
var costume: Outfit? = null
}
@JsonClass(generateAdapter = true)
class Items {
var gear: Gear? = null
}
@JsonClass(generateAdapter = true)
class User: Avatar {
override val currentMount: String? = null
override val currentPet: String? = null

View file

@ -0,0 +1,7 @@
package com.habitrpg.wearos.habitica.models
class WearableHabitResponse<T> {
var data: T? = null
var success: Boolean? = null
var message: String? = null
}

View file

@ -3,7 +3,9 @@ package com.habitrpg.wearos.habitica.models.tasks
import com.habitrpg.common.habitica.models.responses.TaskDirectionData
import com.habitrpg.wearos.habitica.models.Buffs
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class BulkTaskScoringData {
@Json(name="con")
var constitution: Int? = null

View file

@ -2,26 +2,19 @@ package com.habitrpg.wearos.habitica.models.tasks
import android.os.Parcel
import android.os.Parcelable
import com.squareup.moshi.JsonClass
import java.util.UUID
open class ChecklistItem: Parcelable {
var id: String? = null
var text: String? = null
@JsonClass(generateAdapter = true)
open class ChecklistItem constructor(
var id: String? = UUID.randomUUID().toString(),
var text: String? = null,
var completed: Boolean = false
) : Parcelable {
var position: Int = 0
@JvmOverloads constructor(id: String? = null, text: String? = null, completed: Boolean = false) {
this.text = text
if (id?.isNotEmpty() == true) {
this.id = id
} else {
this.id = UUID.randomUUID().toString()
}
this.completed = completed
}
constructor(item: ChecklistItem) {
constructor(item: ChecklistItem) : this() {
this.text = item.text
this.id = item.id
this.completed = item.completed
@ -44,7 +37,7 @@ open class ChecklistItem: Parcelable {
override fun newArray(size: Int): Array<ChecklistItem?> = arrayOfNulls(size)
}
constructor(source: Parcel) {
constructor(source: Parcel) : this() {
id = source.readString()
text = source.readString()
completed = source.readByte() == 1.toByte()

View file

@ -2,7 +2,9 @@ package com.habitrpg.wearos.habitica.models.tasks
import android.os.Parcel
import android.os.Parcelable
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
open class Days(): Parcelable {
var m: Boolean = true
var t: Boolean = true

View file

@ -2,6 +2,7 @@ package com.habitrpg.wearos.habitica.models.tasks
import android.os.Parcel
import android.os.Parcelable
import com.squareup.moshi.JsonClass
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
@ -10,7 +11,8 @@ import java.time.format.DateTimeFormatter
import java.time.format.DateTimeFormatterBuilder
import java.time.temporal.TemporalAccessor
open class RemindersItem : Parcelable {
@JsonClass(generateAdapter = true)
open class RemindersItem constructor() : Parcelable {
var id: String? = null
var startDate: String? = null
var time: String? = null
@ -34,14 +36,12 @@ open class RemindersItem : Parcelable {
override fun newArray(size: Int): Array<RemindersItem?> = arrayOfNulls(size)
}
constructor(source: Parcel) {
constructor(source: Parcel) : this() {
id = source.readString()
startDate = source.readString()
time = source.readString()
}
constructor()
override fun equals(other: Any?): Boolean {
return if (other is RemindersItem) {
this.id == other.id

View file

@ -1,21 +0,0 @@
package com.habitrpg.wearos.habitica.models.tasks
open class Tag {
var id: String = ""
var userId: String? = null
var name: String = ""
internal var challenge: Boolean = false
override fun equals(other: Any?): Boolean {
if (other is Tag) {
return this.id == other.id
}
return super.equals(other)
}
override fun hashCode(): Int {
return id.hashCode()
}
}

View file

@ -9,6 +9,7 @@ import com.habitrpg.common.habitica.models.tasks.Frequency
import com.habitrpg.common.habitica.models.tasks.TaskType
import com.habitrpg.wearos.habitica.R
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.json.JSONArray
import org.json.JSONException
import java.time.LocalDateTime
@ -21,7 +22,8 @@ import java.util.Calendar
import java.util.Date
import java.util.GregorianCalendar
open class Task : Parcelable {
@JsonClass(generateAdapter = true)
open class Task constructor(): Parcelable {
@Json(name="_id")
var id: String? = null
@ -32,7 +34,7 @@ open class Task : Parcelable {
var type: TaskType?
get() = TaskType.from(typeValue)
set(value) { typeValue = value?.value }
private var typeValue: String? = null
internal var typeValue: String? = null
var challengeID: String? = null
var challengeBroken: String? = null
var attribute: Attribute?
@ -40,10 +42,8 @@ open class Task : Parcelable {
set(value) { attributeValue = value?.value }
var attributeValue: String? = Attribute.STRENGTH.value
var value: Double = 0.0
var tags: List<Tag>? = listOf()
var dateCreated: Date? = null
var position: Int = 0
var group: TaskGroupPlan? = null
// Habits
var up: Boolean? = false
var down: Boolean? = false
@ -54,10 +54,7 @@ open class Task : Parcelable {
var checklist: List<ChecklistItem>? = listOf()
var reminders: List<RemindersItem>? = listOf()
// dailies
var frequency: Frequency?
get() = Frequency.from(frequencyValue)
set(value) { frequencyValue = value?.value }
var frequencyValue: String? = null
var frequency: Frequency? = null
var everyX: Int? = 0
var streak: Int? = 0
var startDate: Date? = null
@ -83,8 +80,8 @@ open class Task : Parcelable {
var isCreating: Boolean = false
var yesterDaily: Boolean = true
private var daysOfMonthString: String? = null
private var weeksOfMonthString: String? = null
internal var daysOfMonthString: String? = null
internal var weeksOfMonthString: String? = null
@Json(ignore = true)
private var daysOfMonth: List<Int>? = null
@ -166,16 +163,6 @@ open class Task : Parcelable {
val isChecklistDisplayActive: Boolean
get() = this.checklist?.size != this.completedChecklistCount
val isGroupTask: Boolean
get() = group?.groupID?.isNotBlank() == true
val isPendingApproval: Boolean
get() = (group?.approvalRequired == true && group?.approvalRequested == true && group?.approvalApproved == false)
fun containsAllTagIds(tagIdList: List<String>): Boolean = tags?.mapTo(ArrayList()) { it.id }?.containsAll(tagIdList) ?: false
fun checkIfDue(): Boolean = isDue == true
fun getNextReminderOccurence(oldTime: String?): ZonedDateTime? {
if (oldTime == null) {
return null
@ -269,7 +256,6 @@ open class Task : Parcelable {
checklist != task.checklist -> return true
priority != task.priority -> return true
attribute != task.attribute && attribute != null -> return true
tags != task.tags -> return true
}
if (type == TaskType.HABIT) {
return when {
@ -312,7 +298,6 @@ open class Task : Parcelable {
dest.writeString(this.attribute?.value)
dest.writeString(this.type?.value)
dest.writeDouble(this.value)
dest.writeList(this.tags as? List<*>)
dest.writeLong(this.dateCreated?.time ?: -1)
dest.writeInt(this.position)
dest.writeValue(this.up)
@ -334,9 +319,8 @@ open class Task : Parcelable {
dest.writeInt(this.counterDown ?: 0)
}
constructor()
protected constructor(`in`: Parcel) {
protected constructor(`in`: Parcel): this() {
this.userId = `in`.readString() ?: ""
this.priority = `in`.readValue(Float::class.java.classLoader) as? Float ?: 0f
this.text = `in`.readString() ?: ""
@ -344,8 +328,6 @@ open class Task : Parcelable {
this.attribute = Attribute.from(`in`.readString() ?: "")
this.type = TaskType.from(`in`.readString() ?: "")
this.value = `in`.readDouble()
this.tags = listOf()
`in`.readList(this.tags as List<*>, TaskTag::class.java.classLoader)
val tmpDateCreated = `in`.readLong()
this.dateCreated = if (tmpDateCreated == -1L) null else Date(tmpDateCreated)
this.position = `in`.readInt()

View file

@ -1,19 +0,0 @@
package com.habitrpg.wearos.habitica.models.tasks
import com.squareup.moshi.Json
import java.util.Date
open class TaskGroupPlan {
@Json(name="id")
var groupID: String? = null
var managerNotes: String? = null
var sharedCompletion: String? = null
var assignedDate: Date? = null
var assigningUsername: String? = null
var assignedUsers: List<String> = listOf()
var approvalRequested: Boolean = false
var approvalApproved: Boolean = false
var approvalRequired: Boolean = false
}

View file

@ -1,5 +1,20 @@
package com.habitrpg.wearos.habitica.models.tasks
class TaskList {
var tasks: MutableMap<String, Task> = mutableMapOf()
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
class TaskList(var tasks: MutableMap<String, Task> = mutableMapOf())
class WrappedTasklistAdapter {
@FromJson
fun fromJson(json: List<Task>): TaskList {
val tasks = mutableMapOf<String, Task>()
json.forEach { tasks[it.id ?: ""] = it }
return TaskList(tasks)
}
@ToJson
fun toJson(value: TaskList): List<Task> {
return value.tasks.values.toList()
}
}

View file

@ -1,6 +0,0 @@
package com.habitrpg.wearos.habitica.models.tasks
open class TaskTag {
var tag: Tag? = null
var task: Task? = null
}

View file

@ -6,8 +6,15 @@ import androidx.preference.PreferenceManager
import com.habitrpg.common.habitica.api.HostConfig
import com.habitrpg.common.habitica.helpers.KeyHelper
import com.habitrpg.shared.habitica.HLogger
import com.habitrpg.wearos.habitica.BuildConfig
import com.habitrpg.wearos.habitica.data.ApiClient
import com.habitrpg.wearos.habitica.data.AttributeAdapter
import com.habitrpg.wearos.habitica.data.FrequencyAdapter
import com.habitrpg.wearos.habitica.data.TaskTypeAdapter
import com.habitrpg.wearos.habitica.data.customDateAdapter
import com.habitrpg.wearos.habitica.models.tasks.WrappedTasklistAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -40,13 +47,19 @@ class AppModule {
fun providesConverterFactory(): Converter.Factory {
return MoshiConverterFactory.create(
Moshi.Builder()
.add(WrappedTasklistAdapter())
.add(customDateAdapter)
.add(FrequencyAdapter())
.add(TaskTypeAdapter())
.add(AttributeAdapter())
.addLast(KotlinJsonAdapterFactory())
.build()
).asLenient()
}
@Provides
@Singleton
fun providesApiHelper(
fun providesApiHClient(
hostConfig: HostConfig,
@ApplicationContext context: Context,
converter: Converter.Factory

View file

@ -1,15 +1,14 @@
package com.habitrpg.wearos.habitica.ui.activities
import android.os.Bundle
import android.os.PersistableBundle
import androidx.activity.ComponentActivity
import androidx.viewbinding.ViewBinding
open class BaseActivity<B: ViewBinding> : ComponentActivity() {
protected lateinit var binding: B
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
}
}

View file

@ -1,14 +1,136 @@
package com.habitrpg.wearos.habitica.ui.activities
import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.activity.viewModels
import androidx.appcompat.content.res.AppCompatResources
import androidx.recyclerview.widget.RecyclerView
import androidx.wear.widget.WearableLinearLayoutManager
import com.habitrpg.common.habitica.models.tasks.TaskType
import com.habitrpg.wearos.habitica.R
import com.habitrpg.wearos.habitica.databinding.ActivityMainBinding
import com.habitrpg.wearos.habitica.ui.adapters.HubAdapter
import com.habitrpg.wearos.habitica.ui.viewmodels.MainViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlin.math.abs
data class MenuItem(
val identifier: String,
val title: String,
val icon: Drawable?,
val onClick: () -> Unit
)
@AndroidEntryPoint
class MainActivity : BaseActivity<ActivityMainBinding>() {
val viewModel: MainViewModel by viewModels()
private val adapter = HubAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivityMainBinding.inflate(layoutInflater)
super.onCreate(savedInstanceState)
binding.root.apply {
layoutManager =
WearableLinearLayoutManager(this@MainActivity, HabiticaScrollingLayoutCallback())
adapter = this@MainActivity.adapter
}
}
override fun onStart() {
super.onStart()
binding.root.post {
binding.root.setPaddingRelative(0, (binding.root.height * 0.25).toInt(), 0, (binding.root.height * 0.25).toInt())
binding.root.scrollY = 0
}
adapter.data = listOf(
MenuItem(
"avatar",
"Avatar",
AppCompatResources.getDrawable(this, R.drawable.icon_rewards)
) {
},
MenuItem(
"Stats",
getString(R.string.stats),
AppCompatResources.getDrawable(this, R.drawable.icon_rewards)
) {
},
MenuItem(
"habits",
getString(R.string.habits),
AppCompatResources.getDrawable(this, R.drawable.icon_habits)
) {
openTasklist(TaskType.HABIT)
},
MenuItem(
"dailies",
getString(R.string.dailies),
AppCompatResources.getDrawable(this, R.drawable.icon_dailies)
) {
openTasklist(TaskType.DAILY)
},
MenuItem(
"todos",
getString(R.string.todos),
AppCompatResources.getDrawable(this, R.drawable.icon_todos)
) {
openTasklist(TaskType.TODO)
},
MenuItem(
"rewards",
getString(R.string.rewards),
AppCompatResources.getDrawable(this, R.drawable.icon_rewards)
) {
openTasklist(TaskType.REWARD)
}
)
viewModel.user.observe(this) {
Log.d("MainActivity", "onStart: ${it.currentPet}")
}
}
private fun openTasklist(type: TaskType) {
val intent = Intent(this, TaskListActivity::class.java).apply {
putExtra("type", type.name)
}
startActivity(intent)
}
}
private const val MAX_ICON_PROGRESS = 0.8f
class HabiticaScrollingLayoutCallback : WearableLinearLayoutManager.LayoutCallback() {
private var progressToCenter: Float = 0f
override fun onLayoutFinished(child: View, parent: RecyclerView) {
child.apply {
// Figure out % progress from top to bottom
val centerOffset = height.toFloat() / 2.0f / parent.height.toFloat()
val yRelativeToCenterOffset = y / parent.height + centerOffset
// Normalize for center
progressToCenter = abs(0.5f - yRelativeToCenterOffset) - 0.25f
if (progressToCenter < 0) {
scaleX = 1f
scaleY = 1f
alpha = 1f
return
}
// Adjust to the maximum scale
progressToCenter = Math.min(progressToCenter * 1.5f, MAX_ICON_PROGRESS)
scaleX = 1 - progressToCenter
scaleY = 1 - progressToCenter
alpha = 1 - progressToCenter * 2
}
}
}

View file

@ -0,0 +1,39 @@
package com.habitrpg.wearos.habitica.ui.activities
import android.os.Bundle
import androidx.activity.viewModels
import androidx.wear.widget.WearableLinearLayoutManager
import com.habitrpg.wearos.habitica.databinding.ActivityTasklistBinding
import com.habitrpg.wearos.habitica.models.tasks.Task
import com.habitrpg.wearos.habitica.ui.adapters.TaskListAdapter
import com.habitrpg.wearos.habitica.ui.viewmodels.TaskListViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class TaskListActivity: BaseActivity<ActivityTasklistBinding>() {
private val adapter = TaskListAdapter()
private val viewModel: TaskListViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivityTasklistBinding.inflate(layoutInflater)
super.onCreate(savedInstanceState)
binding.root.apply {
isEdgeItemsCenteringEnabled = true
layoutManager =
WearableLinearLayoutManager(this@TaskListActivity, HabiticaScrollingLayoutCallback())
adapter = this@TaskListActivity.adapter
}
adapter.data = listOf(
Task().apply { text = "Test 1" },
Task().apply { text = "Test 2" },
Task().apply { text = "Test 3" },
Task().apply { text = "Test 4" },
Task().apply { text = "Test 5" }
)
viewModel.tasks.observe(this) {
adapter.data = it
}
}
}

View file

@ -0,0 +1,39 @@
package com.habitrpg.wearos.habitica.ui.adapters
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.wearos.habitica.databinding.RowHubBinding
import com.habitrpg.wearos.habitica.ui.activities.MenuItem
class HubAdapter: RecyclerView.Adapter<HubViewHolder>() {
var data: List<MenuItem> = listOf()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HubViewHolder {
return HubViewHolder(RowHubBinding.inflate(parent.context.layoutInflater, parent, false).root)
}
override fun onBindViewHolder(holder: HubViewHolder, position: Int) {
holder.bind(data[position])
}
override fun getItemCount(): Int {
return data.size
}
}
class HubViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val binding = RowHubBinding.bind(itemView)
fun bind(item: MenuItem) {
binding.title.text = item.title
binding.iconView.setImageDrawable(item.icon)
binding.root.setOnClickListener {
item.onClick()
}
}
}

View file

@ -0,0 +1,35 @@
package com.habitrpg.wearos.habitica.ui.adapters
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.wearos.habitica.databinding.RowHabitBinding
import com.habitrpg.wearos.habitica.models.tasks.Task
class TaskListAdapter: RecyclerView.Adapter<TaskViewHolder>() {
var data: List<Task> = listOf()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
return TaskViewHolder(RowHabitBinding.inflate(parent.context.layoutInflater, parent, false).root)
}
override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
holder.bind(data[position])
}
override fun getItemCount(): Int {
return data.size
}
}
class TaskViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val binding = RowHabitBinding.bind(itemView)
fun bind(task: Task) {
binding.title.text = task.text
}
}

View file

@ -2,11 +2,6 @@ package com.habitrpg.wearos.habitica.ui.viewmodels
import androidx.lifecycle.ViewModel
import com.habitrpg.wearos.habitica.data.repositories.UserRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
open class BaseViewModel: ViewModel() {
@Inject
lateinit var repository: UserRepository
open class BaseViewModel(val userRepository: UserRepository): ViewModel() {
}

View file

@ -0,0 +1,20 @@
package com.habitrpg.wearos.habitica.ui.viewmodels
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.habitrpg.wearos.habitica.data.repositories.UserRepository
import com.habitrpg.wearos.habitica.models.User
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(userRepository: UserRepository) : BaseViewModel(userRepository) {
val user = MutableLiveData<User>()
init {
viewModelScope.launch {
user.value = userRepository.retrieveUser()
}
}
}

View file

@ -0,0 +1,24 @@
package com.habitrpg.wearos.habitica.ui.viewmodels
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.habitrpg.wearos.habitica.data.repositories.TaskRepository
import com.habitrpg.wearos.habitica.data.repositories.UserRepository
import com.habitrpg.wearos.habitica.models.tasks.Task
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class TaskListViewModel @Inject constructor(
private val taskRepository: TaskRepository,
userRepository: UserRepository
) : BaseViewModel(userRepository) {
val tasks = MutableLiveData<List<Task>>()
init {
viewModelScope.launch {
tasks.value = taskRepository.retrieveTasks()?.tasks?.values?.toList()
}
}
}

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="40dp" />
<solid android:color="@color/gray_100" />
</shape>

View file

@ -1,21 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.wear.widget.BoxInsetLayout
<androidx.wear.widget.WearableRecyclerView
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"
tools:context=".ui.activities.MainActivity"
tools:deviceIds="wear">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_boxedEdges="all">
<com.habitrpg.common.habitica.views.AvatarView
android:id="@+id/avatar_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</androidx.wear.widget.BoxInsetLayout>
</androidx.wear.widget.WearableRecyclerView>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.wear.widget.WearableRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.wear.widget.WearableRecyclerView>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/spacing_medium"
android:paddingVertical="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/row_background"
android:padding="@dimen/spacing_medium"
android:gravity="center">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</FrameLayout>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/spacing_medium"
android:paddingVertical="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/row_background"
android:padding="@dimen/spacing_medium"
android:gravity="center">
<ImageView
android:id="@+id/icon_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon_habits"
android:paddingEnd="@dimen/spacing_medium"
android:paddingStart="0dp"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</FrameLayout>

View file

@ -1,3 +0,0 @@
<resources>
<string name="hello_world">Hello Round World!</string>
</resources>

View file

@ -1,8 +1,3 @@
<resources>
<string name="app_name">Habitica</string>
<!--
This string is used for square devices and overridden by hello_world in
values-round/strings.xml for round devices.
-->
<string name="hello_world">Hello Square World!</string>
</resources>