Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mobile 203/user pages 1.3 #455

Merged
merged 7 commits into from
Aug 10, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import java.util.Properties
import java.io.FileInputStream
import java.util.Properties

plugins {
alias(libs.plugins.android.application)
Expand Down Expand Up @@ -210,7 +210,7 @@ dependencies {
implementation(libs.logger.android)

// Charts
implementation(libs.vico.compose)
implementation("com.patrykandpatrick.vico:compose:2.0.0-alpha.22")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have been added to the toml file.


// Testing
testImplementation(libs.junit)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.listenbrainz.android.model.user

import com.google.gson.annotations.SerializedName

data class Artist(
val artist_mbid: String,
val artist_name: String,
val listen_count: Int
@SerializedName("artist_mbid") val artistMbid: String,
@SerializedName("artist_name") val artistName: String,
@SerializedName("listen_count") val listenCount: Int
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.listenbrainz.android.model.user

import com.google.gson.annotations.SerializedName

data class ListeningActivity(
@SerializedName("from_ts") val fromTs: Int? = null,
@SerializedName("listen_count") val listenCount: Int? = null,
@SerializedName("time_range") val timeRange: String? = null,
@SerializedName("to_ts") val toTs: Int? = null,
var componentIndex: Int? = null,
var color: Int? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.listenbrainz.android.model.user

import com.google.gson.annotations.SerializedName

data class ListeningActivityPayload(
@SerializedName("from_ts") val fromTs: Int? = null,
@SerializedName("last_updated") val lastUpdated: Int? = null,
@SerializedName("listening_activity") val listeningActivity: List<ListeningActivity?>? = null,
val range: String? = null,
@SerializedName("to_ts") val toTs: Int? = null,
@SerializedName("user_id") val userId: String? = null,
)
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package org.listenbrainz.android.model.user

import com.google.gson.annotations.SerializedName

data class TopArtistsPayload(
val artists: List<Artist>,
val count: Int,
val from_ts: Int,
val last_updated: Int,
val offset: Int,
val range: String,
val to_ts: Int,
val total_artist_count: Int,
val user_id: String
val artists: List<Artist>,
val count: Int,
@SerializedName("from_ts") val fromTs: Int,
@SerializedName("last_updated") val lastUpdated: Int,
val offset: Int,
val range: String,
@SerializedName("to_ts") val toTs: Int,
@SerializedName("total_artist_count") val totalArtistCount: Int,
@SerializedName("user_id") val userId: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.listenbrainz.android.model.user

data class UserListeningActivity(
val payload: ListeningActivityPayload? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.listenbrainz.android.model.PinnedRecording
import org.listenbrainz.android.model.user.AllPinnedRecordings
import org.listenbrainz.android.model.user.TopArtists
import org.listenbrainz.android.model.user.UserFeedback
import org.listenbrainz.android.model.user.UserListeningActivity
import org.listenbrainz.android.model.user.UserSimilarityPayload
import org.listenbrainz.android.util.Resource

Expand All @@ -14,6 +15,7 @@ interface UserRepository {
suspend fun fetchUserCurrentPins(username: String?) : Resource<PinnedRecording?>
suspend fun fetchUserPins(username: String?) : Resource<AllPinnedRecordings?>
//TODO: Move to artists VM once implemented
suspend fun getTopArtists(username: String?): Resource<TopArtists>
suspend fun getTopArtists(username: String?, rangeString: String = "all_time", count: Int = 25): Resource<TopArtists>
suspend fun getUserFeedback(username: String?, score: Int?): Resource<UserFeedback?>
suspend fun getUserListeningActivity(username: String?, rangeString: String = "all_time"): Resource<UserListeningActivity?>
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.listenbrainz.android.model.ResponseError
import org.listenbrainz.android.model.user.AllPinnedRecordings
import org.listenbrainz.android.model.user.TopArtists
import org.listenbrainz.android.model.user.UserFeedback
import org.listenbrainz.android.model.user.UserListeningActivity
import org.listenbrainz.android.model.user.UserSimilarityPayload
import org.listenbrainz.android.service.UserService
import org.listenbrainz.android.util.Resource
Expand Down Expand Up @@ -37,16 +38,26 @@ class UserRepositoryImpl @Inject constructor(
}

override suspend fun getTopArtists(
username: String?
username: String?,
rangeString: String,
count: Int
): Resource<TopArtists> = parseResponse {
if(username.isNullOrEmpty()) return ResponseError.BAD_REQUEST.asResource()
service.getTopArtistsOfUser(username)
service.getTopArtistsOfUser(username, rangeString, count)
}

override suspend fun getUserFeedback(username: String?, score: Int?): Resource<UserFeedback?> = parseResponse {
if(username.isNullOrEmpty()) return ResponseError.BAD_REQUEST.asResource()
service.getUserFeedback(username, score)
}

override suspend fun getUserListeningActivity(
username: String?,
rangeString: String
): Resource<UserListeningActivity?> = parseResponse {
if(username.isNullOrEmpty()) return ResponseError.BAD_REQUEST.asResource()
service.getUserListeningActivity(username,rangeString)
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.listenbrainz.android.model.PinnedRecording
import org.listenbrainz.android.model.user.AllPinnedRecordings
import org.listenbrainz.android.model.user.TopArtists
import org.listenbrainz.android.model.user.UserFeedback
import org.listenbrainz.android.model.user.UserListeningActivity
import org.listenbrainz.android.model.user.UserSimilarityPayload
import retrofit2.Response
import retrofit2.http.GET
Expand All @@ -24,9 +25,12 @@ interface UserService {
@GET("{user_name}/pins")
suspend fun getUserPins(@Path("user_name") username: String?) : Response<AllPinnedRecordings?>

@GET("stats/user/{user_name}/artists?count=100")
suspend fun getTopArtistsOfUser(@Path("user_name") username: String?) : Response<TopArtists>
@GET("stats/user/{user_name}/artists")
suspend fun getTopArtistsOfUser(@Path("user_name") username: String?, @Query("range") rangeString: String?, @Query("count") count: Int = 25) : Response<TopArtists>

@GET("feedback/user/{user_name}/get-feedback?metadata=true")
suspend fun getUserFeedback(@Path("user_name") username: String?, @Query("score") score: Int?) : Response<UserFeedback?>

@GET("stats/user/{user_name}/listening-activity")
suspend fun getUserListeningActivity(@Path("user_name") username: String?, @Query("range") rangeString: String?): Response<UserListeningActivity?>
}
104 changes: 55 additions & 49 deletions app/src/main/java/org/listenbrainz/android/ui/components/YimGraph.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,73 +6,79 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.patrykandpatrick.vico.compose.axis.horizontal.rememberBottomAxis
import com.patrykandpatrick.vico.compose.axis.vertical.rememberStartAxis
import com.patrykandpatrick.vico.compose.chart.Chart
import com.patrykandpatrick.vico.compose.chart.column.columnChart
import com.patrykandpatrick.vico.core.component.shape.LineComponent
import com.patrykandpatrick.vico.core.component.text.textComponent
import com.patrykandpatrick.vico.core.entry.ChartEntry
import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer
import com.patrykandpatrick.vico.core.entry.entryOf
import androidx.compose.ui.unit.sp
import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberBottomAxis
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberStartAxis
import com.patrykandpatrick.vico.compose.cartesian.layer.rememberColumnCartesianLayer
import com.patrykandpatrick.vico.compose.cartesian.rememberCartesianChart
import com.patrykandpatrick.vico.compose.common.component.rememberLineComponent
import com.patrykandpatrick.vico.compose.common.component.rememberTextComponent
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
import com.patrykandpatrick.vico.core.cartesian.data.columnSeries
import com.patrykandpatrick.vico.core.cartesian.layer.ColumnCartesianLayer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext

@Composable
fun YimGraph (yearListens : List<Pair<String , Int>>) {

val chartEntries: MutableList<ChartEntry> = remember { mutableListOf() }

for (i in 1..yearListens.size) {
chartEntries.add(
entryOf(
yearListens[i - 1].first.toInt().toFloat(),
yearListens[i - 1].second
)
)
val modelProducer = remember {
CartesianChartModelProducer()
}
val chartEntryModelProducer = ChartEntryModelProducer(chartEntries)
LaunchedEffect(Unit) {
withContext(Dispatchers.Default) {
while(isActive){
modelProducer.runTransaction { columnSeries {
series(x = yearListens.map { it.first.toInt() }, y = yearListens.map { it.second })
} }
}

}
}

Chart(
CartesianChartHost(
modifier = Modifier
.padding(start = 11.dp, end = 11.dp)
.height(250.dp)
.clip(RoundedCornerShape(10.dp))
.background(Color(0xFFe0e5de)),
chart = columnChart(
spacing = 1.dp,
columns = List(yearListens.size) {
LineComponent(
color = 0xFFe36b3c.toInt(),
thicknessDp = 25f,
)
},
),
chartModelProducer = chartEntryModelProducer,
startAxis = rememberStartAxis(
valueFormatter = { value, _ ->
value.toInt().toString()
}
),
bottomAxis = rememberBottomAxis(
label = textComponent {
this.ellipsize = TextUtils.TruncateAt.MARQUEE
this.textSizeSp = 11f
},
guideline = null,
valueFormatter = { value, _ ->
if (value.toInt() % 5 == 0) {
value.toInt().toString()
} else {
""
chart = rememberCartesianChart(
rememberColumnCartesianLayer(
ColumnCartesianLayer.ColumnProvider.series(
rememberLineComponent(
color = Color(0xFFe36b3c),
thickness = 25.dp,
)
),
spacing = 1.dp
),
startAxis = rememberStartAxis(),
bottomAxis = rememberBottomAxis(
label = rememberTextComponent (
ellipsize = TextUtils.TruncateAt.MARQUEE,
textSize = 11.sp
),
guideline = null,
valueFormatter = CartesianValueFormatter { value, chartValues, verticalAxisPosition ->
if(value.toInt() % 5 == 0){
value.toString()
}
else{
""
}
}
},
),
),
modelProducer = modelProducer
)


)
}
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ fun BaseProfileScreen(
username = username
)
ProfileScreenTab.STATS -> StatsScreen(

username = username
)
ProfileScreenTab.TASTE -> TasteScreen(
snackbarState = snackbarState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import org.listenbrainz.android.model.ListenBitmap
import org.listenbrainz.android.model.PinnedRecording
import org.listenbrainz.android.model.SimilarUser
import org.listenbrainz.android.model.user.AllPinnedRecordings
import org.listenbrainz.android.model.user.ListeningActivity
import org.listenbrainz.android.model.user.UserFeedback
import org.listenbrainz.android.ui.screens.profile.listens.ListeningNowUiState
import org.listenbrainz.android.ui.screens.profile.stats.StatsRange
import org.listenbrainz.android.ui.screens.profile.stats.UserGlobal

data class ProfileUiState(
val isSelf: Boolean = false,
Expand Down Expand Up @@ -41,6 +44,8 @@ data class TasteTabUIState (

data class StatsTabUIState(
val isLoading: Boolean = true,
val userListeningActivity: Map<Pair<UserGlobal, StatsRange>, List<ListeningActivity?>> = mapOf(),
val sortedListeningActivity: List<ListeningActivity?>? = listOf()
)

data class ListeningNowUiState(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enum class CategoryState {
ARTISTS,
ALBUMS,
SONGS
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.listenbrainz.android.ui.screens.profile.stats

enum class StatsRange (val rangeString: String, val apiIdenfier: String){
THIS_WEEK("This Week", "this_week"),
THIS_MONTH("This Month", "this_month"),
THIS_YEAR("This Year", "this_year"),
LAST_WEEK("Tast Week", "week"),
LAST_MONTH("Last Month", "month"),
LAST_YEAR("Last Year", "year"),
ALL_TIME("All Time", "all_time"),
}
Loading