mirror of
https://gitlab.shinice.net/pixeldroid/PixelDroid
synced 2025-02-07 15:18:46 +01:00
More progress on stories :)
This commit is contained in:
parent
f60889ea14
commit
73193abd95
@ -138,7 +138,7 @@ dependencies {
|
||||
*/
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
implementation 'androidx.core:core-ktx:1.10.0'
|
||||
implementation 'androidx.core:core-ktx:1.10.1'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
|
||||
@ -186,7 +186,7 @@ dependencies {
|
||||
|
||||
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
|
||||
|
||||
implementation 'com.google.android.material:material:1.8.0'
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
|
||||
//Dagger (dependency injection)
|
||||
implementation 'com.google.dagger:dagger-android:2.45'
|
||||
|
@ -11,6 +11,7 @@ import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.core.text.toSpanned
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.pixeldroid.app.R
|
||||
import org.pixeldroid.app.utils.api.PixelfedAPI
|
||||
import org.pixeldroid.app.utils.api.objects.Account.Companion.openAccountFromId
|
||||
@ -106,7 +107,7 @@ fun parseHTMLText(
|
||||
override fun onClick(widget: View) {
|
||||
|
||||
// Retrieve the account for the given profile
|
||||
lifecycleScope.launchWhenCreated {
|
||||
lifecycleScope.launch {
|
||||
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
|
||||
openAccountFromId(accountId, api, context)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.pixeldroid.app.searchDiscover
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.SearchManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@ -8,25 +9,38 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.google.android.material.carousel.CarouselLayoutManager
|
||||
import kotlinx.coroutines.launch
|
||||
import org.pixeldroid.app.databinding.FragmentSearchBinding
|
||||
import org.pixeldroid.app.databinding.StoryCarouselBinding
|
||||
import org.pixeldroid.app.searchDiscover.TrendingActivity.Companion.TRENDING_TAG
|
||||
import org.pixeldroid.app.searchDiscover.TrendingActivity.Companion.TrendingType
|
||||
import org.pixeldroid.app.utils.api.PixelfedAPI
|
||||
import org.pixeldroid.app.stories.StoriesActivity
|
||||
import org.pixeldroid.app.stories.StoriesActivity.Companion.STORY_CAROUSEL
|
||||
import org.pixeldroid.app.stories.StoriesActivity.Companion.STORY_CAROUSEL_USER_ID
|
||||
import org.pixeldroid.app.utils.BaseFragment
|
||||
import org.pixeldroid.app.utils.api.PixelfedAPI
|
||||
import org.pixeldroid.app.utils.api.objects.CarouselUserContainer
|
||||
import org.pixeldroid.app.utils.api.objects.StoryCarousel
|
||||
import org.pixeldroid.app.utils.bindingLifecycleAware
|
||||
|
||||
|
||||
/**
|
||||
* This fragment lets you search and use Pixelfed's Discover feature
|
||||
*/
|
||||
|
||||
class SearchDiscoverFragment : BaseFragment() {
|
||||
|
||||
private lateinit var api: PixelfedAPI
|
||||
|
||||
var binding: FragmentSearchBinding by bindingLifecycleAware()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
binding = FragmentSearchBinding.inflate(inflater, container, false)
|
||||
|
||||
@ -37,6 +51,13 @@ class SearchDiscoverFragment : BaseFragment() {
|
||||
isSubmitButtonEnabled = true
|
||||
}
|
||||
|
||||
val adapter = StoriesListAdapter(::onClickStory)
|
||||
binding.recyclerView2.adapter = adapter
|
||||
|
||||
loadStories(adapter)
|
||||
|
||||
binding.recyclerView2.layoutManager = CarouselLayoutManager()
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@ -56,4 +77,69 @@ class SearchDiscoverFragment : BaseFragment() {
|
||||
intent.putExtra(TRENDING_TAG, type)
|
||||
ContextCompat.startActivity(binding.root.context, intent, null)
|
||||
}
|
||||
|
||||
private fun onClickStory(carousel: StoryCarousel, userId: String){
|
||||
val intent = Intent(requireContext(), StoriesActivity::class.java)
|
||||
intent.putExtra(STORY_CAROUSEL, carousel)
|
||||
intent.putExtra(STORY_CAROUSEL_USER_ID, userId)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun loadStories(adapter: StoriesListAdapter) {
|
||||
lifecycleScope.launch {
|
||||
try{
|
||||
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
||||
val carousel = api.carousel()
|
||||
adapter.initCarousel(carousel)
|
||||
} catch (exception: Exception){
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class StoriesListAdapter(private val listener: (StoryCarousel, String) -> Unit): RecyclerView.Adapter<StoriesListAdapter.ViewHolder>() {
|
||||
|
||||
private var storyCarousel: StoryCarousel? = null
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val v = StoryCarouselBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return ViewHolder(v)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
storyCarousel?.nodes?.get(position)?.let { holder.bindItem(it) }
|
||||
holder.itemView.setOnClickListener {
|
||||
storyCarousel?.let { carousel ->
|
||||
storyCarousel?.nodes?.get(position)?.user?.id?.let { userId ->
|
||||
listener(
|
||||
carousel,
|
||||
userId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return storyCarousel?.nodes?.size ?: 0
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun initCarousel(carousel: StoryCarousel){
|
||||
storyCarousel = carousel
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
|
||||
class ViewHolder(var itemBinding: StoryCarouselBinding) :
|
||||
RecyclerView.ViewHolder(itemBinding.root) {
|
||||
fun bindItem(user: CarouselUserContainer) {
|
||||
Glide.with(itemBinding.root).load(user.nodes?.firstOrNull()?.src).into(itemBinding.carouselImageView)
|
||||
Glide.with(itemBinding.root).load(user.user?.avatar).circleCrop().into(itemBinding.storyAuthorProfilePicture)
|
||||
|
||||
itemBinding.username.text = user.user?.username ?: "" //TODO check which one to use here!
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,9 @@ package org.pixeldroid.app.stories
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.MotionEvent
|
||||
import android.view.View.OnClickListener
|
||||
import android.view.View.OnTouchListener
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
@ -21,10 +24,18 @@ import org.pixeldroid.app.R
|
||||
import org.pixeldroid.app.databinding.ActivityStoriesBinding
|
||||
import org.pixeldroid.app.posts.setTextViewFromISO8601
|
||||
import org.pixeldroid.app.utils.BaseThemedWithoutBarActivity
|
||||
import org.pixeldroid.app.utils.api.objects.Account
|
||||
import org.pixeldroid.app.utils.api.objects.StoryCarousel
|
||||
|
||||
|
||||
class StoriesActivity: BaseThemedWithoutBarActivity() {
|
||||
|
||||
companion object {
|
||||
const val STORY_CAROUSEL = "LaunchStoryCarousel"
|
||||
const val STORY_CAROUSEL_USER_ID = "LaunchStoryUserId"
|
||||
}
|
||||
|
||||
|
||||
private lateinit var binding: ActivityStoriesBinding
|
||||
|
||||
private lateinit var model: StoriesViewModel
|
||||
@ -32,11 +43,14 @@ class StoriesActivity: BaseThemedWithoutBarActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val carousel = intent.getSerializableExtra(STORY_CAROUSEL) as StoryCarousel
|
||||
val userId = intent.getStringExtra(STORY_CAROUSEL_USER_ID)
|
||||
|
||||
binding = ActivityStoriesBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
val _model: StoriesViewModel by viewModels {
|
||||
StoriesViewModelFactory(application)
|
||||
StoriesViewModelFactory(application, carousel, userId)
|
||||
}
|
||||
model = _model
|
||||
|
||||
@ -48,7 +62,7 @@ class StoriesActivity: BaseThemedWithoutBarActivity() {
|
||||
uiState.age?.let { setTextViewFromISO8601(it, binding.storyAge, false) }
|
||||
|
||||
if (uiState.errorMessage != null) {
|
||||
binding.storyErrorText.text = uiState.errorMessage
|
||||
binding.storyErrorText.setText(uiState.errorMessage)
|
||||
binding.storyErrorCard.isVisible = true
|
||||
} else binding.storyErrorCard.isVisible = false
|
||||
|
||||
@ -73,6 +87,9 @@ class StoriesActivity: BaseThemedWithoutBarActivity() {
|
||||
|
||||
binding.storyAuthor.text = uiState.username
|
||||
|
||||
binding.carouselProgress.text = getString(R.string.storyProgress)
|
||||
.format(uiState.currentImage + 1, uiState.imageList.size)
|
||||
|
||||
uiState.imageList.getOrNull(uiState.currentImage)?.let {
|
||||
Glide.with(binding.storyImage)
|
||||
.load(it)
|
||||
@ -91,7 +108,9 @@ class StoriesActivity: BaseThemedWithoutBarActivity() {
|
||||
dataSource: DataSource?,
|
||||
isFirstResource: Boolean,
|
||||
): Boolean {
|
||||
model.imageLoaded()
|
||||
Glide.with(binding.storyImage)
|
||||
.load(uiState.imageList.getOrNull(uiState.currentImage + 1))
|
||||
.preload()
|
||||
return false
|
||||
}
|
||||
})
|
||||
@ -100,6 +119,19 @@ class StoriesActivity: BaseThemedWithoutBarActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Pause when clicked on text field
|
||||
binding.storyReplyField.editText?.setOnFocusChangeListener { view, isFocused ->
|
||||
if (view.isInTouchMode && isFocused) {
|
||||
view.performClick() // picks up first tap
|
||||
}
|
||||
}
|
||||
binding.storyReplyField.editText?.setOnClickListener {
|
||||
if (!model.uiState.value.paused) {
|
||||
model.pause()
|
||||
}
|
||||
}
|
||||
|
||||
binding.storyReplyField.editText?.doAfterTextChanged {
|
||||
it?.let { text ->
|
||||
val string = text.toString()
|
||||
@ -134,10 +166,60 @@ class StoriesActivity: BaseThemedWithoutBarActivity() {
|
||||
//Set the button's appearance
|
||||
it.isSelected = !it.isSelected
|
||||
model.pause()
|
||||
}
|
||||
|
||||
val authorOnClickListener = OnClickListener {
|
||||
if (!model.uiState.value.paused) {
|
||||
model.pause()
|
||||
}
|
||||
model.currentProfileId()?.let {
|
||||
lifecycleScope.launch {
|
||||
Account.openAccountFromId(
|
||||
it,
|
||||
apiHolder.api ?: apiHolder.setToCurrentUser(),
|
||||
this@StoriesActivity
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.storyAuthorProfilePicture.setOnClickListener(authorOnClickListener)
|
||||
binding.storyAuthor.setOnClickListener(authorOnClickListener)
|
||||
|
||||
val onTouchListener = OnTouchListener { v, event ->
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> if (!model.uiState.value.paused) {
|
||||
model.pause()
|
||||
}
|
||||
MotionEvent.ACTION_UP -> if(event.eventTime - event.downTime < 500) {
|
||||
v.performClick()
|
||||
return@OnTouchListener false
|
||||
} else model.pause()
|
||||
}
|
||||
|
||||
binding.storyImage.setOnClickListener {
|
||||
model.pause()
|
||||
true
|
||||
}
|
||||
|
||||
binding.viewMiddle.setOnTouchListener{ v, event ->
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> model.pause()
|
||||
MotionEvent.ACTION_UP -> if(event.eventTime - event.downTime < 500) {
|
||||
v.performClick()
|
||||
return@setOnTouchListener false
|
||||
} else model.pause()
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
binding.viewLeft.setOnTouchListener(onTouchListener)
|
||||
binding.viewRight.setOnTouchListener(onTouchListener)
|
||||
|
||||
//TODO implement hold to pause
|
||||
|
||||
binding.viewRight.setOnClickListener {
|
||||
model.goToNext()
|
||||
}
|
||||
binding.viewLeft.setOnClickListener {
|
||||
model.goToPrevious()
|
||||
}
|
||||
}
|
||||
}
|
@ -3,13 +3,12 @@ package org.pixeldroid.app.stories
|
||||
import android.app.Application
|
||||
import android.os.CountDownTimer
|
||||
import android.text.Editable
|
||||
import android.util.Log
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
@ -29,40 +28,45 @@ data class StoriesUiState(
|
||||
val imageList: List<String> = emptyList(),
|
||||
val durationList: List<Int> = emptyList(),
|
||||
val paused: Boolean = false,
|
||||
val errorMessage: String? = null,
|
||||
val snackBar: String? = null,
|
||||
@StringRes
|
||||
val errorMessage: Int? = null,
|
||||
@StringRes
|
||||
val snackBar: Int? = null,
|
||||
val reply: String = ""
|
||||
)
|
||||
|
||||
class StoriesViewModel(
|
||||
application: Application,
|
||||
val carousel: StoryCarousel,
|
||||
userId: String?
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
@Inject
|
||||
lateinit var apiHolder: PixelfedAPIHolder
|
||||
|
||||
private val _uiState: MutableStateFlow<StoriesUiState> = MutableStateFlow(StoriesUiState())
|
||||
private var currentAccount = carousel.nodes?.firstOrNull { it?.user?.id == userId }
|
||||
|
||||
private val _uiState: MutableStateFlow<StoriesUiState> = MutableStateFlow(
|
||||
newUiStateFromCurrentAccount()
|
||||
)
|
||||
|
||||
val uiState: StateFlow<StoriesUiState> = _uiState
|
||||
|
||||
var carousel: StoryCarousel? = null
|
||||
|
||||
val count = MutableLiveData<Long>()
|
||||
val count = MutableLiveData<Float>()
|
||||
|
||||
private var timer: CountDownTimer? = null
|
||||
|
||||
init {
|
||||
(application as PixelDroidApplication).getAppComponent().inject(this)
|
||||
loadStories()
|
||||
startTimerForCurrent()
|
||||
}
|
||||
|
||||
private fun setTimer(timerLength: Long) {
|
||||
private fun setTimer(timerLength: Float) {
|
||||
count.value = timerLength
|
||||
timer = object: CountDownTimer(timerLength * 1000, 500){
|
||||
timer = object: CountDownTimer((timerLength * 1000).toLong(), 100){
|
||||
|
||||
override fun onTick(millisUntilFinished: Long) {
|
||||
count.value = millisUntilFinished / 1000
|
||||
Log.e("Timer second", "${count.value}")
|
||||
count.value = millisUntilFinished.toFloat() / 1000
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
@ -71,63 +75,63 @@ class StoriesViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun goToNext(){
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(
|
||||
currentImage = currentUiState.currentImage + 1,
|
||||
//TODO don't just take the first here, choose from activity input somehow?
|
||||
age = carousel?.nodes?.firstOrNull()?.nodes?.getOrNull(currentUiState.currentImage + 1)?.created_at
|
||||
)
|
||||
private fun newUiStateFromCurrentAccount(): StoriesUiState = StoriesUiState(
|
||||
profilePicture = currentAccount?.user?.avatar,
|
||||
age = currentAccount?.nodes?.getOrNull(0)?.created_at,
|
||||
username = currentAccount?.user?.username, //TODO check if not username_acct, think about falling back on other option?
|
||||
errorMessage = null,
|
||||
currentImage = 0,
|
||||
imageList = currentAccount?.nodes?.mapNotNull { it?.src } ?: emptyList(),
|
||||
durationList = currentAccount?.nodes?.mapNotNull { it?.duration } ?: emptyList()
|
||||
)
|
||||
|
||||
private fun goTo(index: Int){
|
||||
if((0 until uiState.value.imageList.size).contains(index)) {
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(
|
||||
currentImage = index,
|
||||
age = currentAccount?.nodes?.getOrNull(index)?.created_at,
|
||||
paused = false
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val currentUserId = currentAccount?.user?.id
|
||||
val currentAccountIndex = carousel.nodes?.indexOfFirst { it?.user?.id == currentUserId } ?: return
|
||||
currentAccount = when (index) {
|
||||
uiState.value.imageList.size -> {
|
||||
// Go to next user
|
||||
if(currentAccountIndex + 1 >= carousel.nodes.size) return
|
||||
carousel.nodes.getOrNull(currentAccountIndex + 1)
|
||||
|
||||
}
|
||||
|
||||
-1 -> {
|
||||
// Go to previous user
|
||||
if(currentAccountIndex <= 0) return
|
||||
carousel.nodes.getOrNull(currentAccountIndex - 1)
|
||||
}
|
||||
else -> return // Do nothing, given index does not make sense
|
||||
}
|
||||
_uiState.update { newUiStateFromCurrentAccount() }
|
||||
}
|
||||
//TODO when done with viewing all stories, close activity and move to profile (?)
|
||||
|
||||
timer?.cancel()
|
||||
startTimerForCurrent()
|
||||
}
|
||||
|
||||
private fun loadStories() {
|
||||
viewModelScope.launch {
|
||||
try{
|
||||
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
||||
carousel = api.carousel()
|
||||
fun goToNext() = goTo(uiState.value.currentImage + 1)
|
||||
|
||||
//TODO don't just take the first here, choose from activity input somehow?
|
||||
val chosenAccount = carousel?.nodes?.firstOrNull()
|
||||
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(
|
||||
profilePicture = chosenAccount?.user?.avatar,
|
||||
age = chosenAccount?.nodes?.getOrNull(0)?.created_at,
|
||||
username = chosenAccount?.user?.username, //TODO check if not username_acct, think about falling back on other option?
|
||||
errorMessage = null,
|
||||
currentImage = 0,
|
||||
imageList = chosenAccount?.nodes?.mapNotNull { it?.src } ?: emptyList(),
|
||||
durationList = chosenAccount?.nodes?.mapNotNull { it?.duration } ?: emptyList()
|
||||
)
|
||||
}
|
||||
startTimerForCurrent()
|
||||
} catch (exception: Exception){
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(errorMessage = "Something went wrong fetching the carousel")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fun goToPrevious() = goTo(uiState.value.currentImage - 1)
|
||||
|
||||
private fun startTimerForCurrent(){
|
||||
uiState.value.let {
|
||||
it.durationList.getOrNull(it.currentImage)?.toLong()?.let { time ->
|
||||
setTimer(time)
|
||||
setTimer(time.toFloat())
|
||||
timer?.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun imageLoaded() {/*
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(currentImage = currentUiState.currentImage + 1)
|
||||
}*/
|
||||
}
|
||||
|
||||
fun pause() {
|
||||
if(_uiState.value.paused){
|
||||
timer?.start()
|
||||
@ -144,16 +148,15 @@ class StoriesViewModel(
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
||||
//TODO don't just take the first here, choose from activity input somehow?
|
||||
val id = carousel?.nodes?.firstOrNull()?.nodes?.getOrNull(uiState.value.currentImage)?.id
|
||||
val id = currentAccount?.nodes?.getOrNull(uiState.value.currentImage)?.id
|
||||
id?.let { api.storyComment(it, text.toString()) }
|
||||
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(snackBar = "Sent reply")
|
||||
currentUiState.copy(snackBar = R.string.sent_reply_story)
|
||||
}
|
||||
} catch (exception: Exception){
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(errorMessage = "Something went wrong sending reply")
|
||||
currentUiState.copy(errorMessage = R.string.story_reply_error)
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,10 +180,17 @@ class StoriesViewModel(
|
||||
currentUiState.copy(snackBar = null)
|
||||
}
|
||||
}
|
||||
|
||||
fun currentProfileId(): String? = currentAccount?.user?.id
|
||||
|
||||
}
|
||||
|
||||
class StoriesViewModelFactory(val application: Application) : ViewModelProvider.Factory {
|
||||
class StoriesViewModelFactory(
|
||||
val application: Application,
|
||||
val carousel: StoryCarousel,
|
||||
val userId: String?
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return modelClass.getConstructor(Application::class.java).newInstance(application)
|
||||
return modelClass.getConstructor(Application::class.java, StoryCarousel::class.java, String::class.java).newInstance(application, carousel, userId)
|
||||
}
|
||||
}
|
||||
|
@ -57,11 +57,13 @@ data class Account(
|
||||
suspend fun openAccountFromId(id: String, api : PixelfedAPI, context: Context) {
|
||||
val account = try {
|
||||
api.getAccount(id)
|
||||
} catch (exception: IOException) {
|
||||
Log.e("GET ACCOUNT ERROR", exception.toString())
|
||||
return
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("ERROR CODE", exception.code().toString())
|
||||
} catch (exception: Exception) {
|
||||
val toLog = if (exception is HttpException) {
|
||||
exception.code().toString()
|
||||
} else {
|
||||
exception.toString()
|
||||
}
|
||||
Log.e("GET ACCOUNT ERROR", toLog)
|
||||
return
|
||||
}
|
||||
//Open the account page in a separate activity
|
||||
|
@ -1,11 +1,12 @@
|
||||
package org.pixeldroid.app.utils.api.objects
|
||||
|
||||
import java.io.Serializable
|
||||
import java.time.Instant
|
||||
|
||||
data class StoryCarousel(
|
||||
val self: CarouselUserContainer?,
|
||||
val nodes: List<CarouselUserContainer?>?
|
||||
)
|
||||
): Serializable
|
||||
|
||||
data class CarouselUser(
|
||||
val id: String?,
|
||||
@ -14,7 +15,7 @@ data class CarouselUser(
|
||||
val avatar: String?, // URL to account avatar
|
||||
val local: Boolean?, // Is this story from the local instance?
|
||||
val is_author: Boolean?, // Is this me? (seems redundant with id)
|
||||
)
|
||||
): Serializable
|
||||
|
||||
/**
|
||||
* Container with a description of the [user] and a list of stories ([nodes])
|
||||
@ -22,7 +23,7 @@ data class CarouselUser(
|
||||
data class CarouselUserContainer(
|
||||
val user: CarouselUser?,
|
||||
val nodes: List<Story?>?,
|
||||
)
|
||||
): Serializable
|
||||
|
||||
data class Story(
|
||||
val id: String?,
|
||||
@ -32,4 +33,4 @@ data class Story(
|
||||
val duration: Int?, //Time in seconds that the Story should be shown
|
||||
val seen: Boolean?, //Indication of whether this story has been seen. Set to true using carouselSeen
|
||||
val created_at: Instant?, //ISO 8601 Datetime
|
||||
)
|
||||
): Serializable
|
@ -54,11 +54,13 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="@string/story_image"
|
||||
tools:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/progressBarStory"
|
||||
app:layout_constraintVertical_bias="1.0"
|
||||
tools:scaleType="centerCrop"
|
||||
tools:srcCompat="@tools:sample/backgrounds/scenic[10]" />
|
||||
|
||||
<ImageButton
|
||||
@ -79,6 +81,7 @@
|
||||
android:id="@+id/progressBarStory"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:progress="0"
|
||||
tools:progress="56"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="visible"
|
||||
@ -133,5 +136,45 @@
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/carouselProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="24dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/pause"
|
||||
app:layout_constraintEnd_toStartOf="@+id/pause"
|
||||
app:layout_constraintTop_toTopOf="@+id/pause"
|
||||
tools:text="2/3" />
|
||||
|
||||
<View
|
||||
android:id="@+id/viewRight"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/viewMiddle"
|
||||
app:layout_constraintBottom_toTopOf="@id/storyReplyField"
|
||||
app:layout_constraintTop_toBottomOf="@+id/storyAuthorProfilePicture" />
|
||||
|
||||
|
||||
<View
|
||||
android:id="@+id/viewMiddle"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/viewRight"
|
||||
app:layout_constraintStart_toEndOf="@id/viewLeft"
|
||||
app:layout_constraintBottom_toTopOf="@id/storyReplyField"
|
||||
app:layout_constraintTop_toBottomOf="@+id/storyAuthorProfilePicture" />
|
||||
|
||||
|
||||
<View
|
||||
android:id="@+id/viewLeft"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/viewMiddle"
|
||||
app:layout_constraintBottom_toTopOf="@id/storyReplyField"
|
||||
app:layout_constraintTop_toBottomOf="@+id/storyAuthorProfilePicture" />
|
||||
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,9 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<ScrollView 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="wrap_content"
|
||||
android:layout_gravity="center_horizontal">
|
||||
android:layout_height="match_parent">
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.SearchView
|
||||
android:id="@+id/search"
|
||||
@ -22,14 +25,14 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/search">
|
||||
app:layout_constraintTop_toBottomOf="@+id/recyclerView2">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/trendingCardView"
|
||||
style="?attr/materialCardViewElevatedStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
style="?attr/materialCardViewElevatedStyle"
|
||||
app:cardBackgroundColor="?attr/colorSecondaryContainer"
|
||||
app:layout_constraintBottom_toTopOf="@+id/hashtagsCardView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@ -45,28 +48,28 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="4dp"
|
||||
android:text="@string/trending_posts"
|
||||
android:textAppearance="?attr/textAppearanceTitleLarge"
|
||||
android:drawablePadding="4dp"
|
||||
android:textColor="?attr/colorOnSecondaryContainer"
|
||||
app:drawableLeftCompat="@drawable/baseline_auto_graph_24" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/daily_trending"
|
||||
android:textColor="?attr/colorOnSecondaryContainer"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||
android:layout_marginTop="8dp" />
|
||||
android:textColor="?attr/colorOnSecondaryContainer" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/hashtagsCardView"
|
||||
style="?attr/materialCardViewElevatedStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
style="?attr/materialCardViewElevatedStyle"
|
||||
app:cardBackgroundColor="?attr/colorSecondaryContainer"
|
||||
app:layout_constraintBottom_toTopOf="@id/accountsCardView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@ -82,33 +85,33 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="4dp"
|
||||
android:text="@string/trending_hashtags"
|
||||
android:textAppearance="?attr/textAppearanceTitleLarge"
|
||||
android:drawablePadding="4dp"
|
||||
android:textColor="?attr/colorOnSecondaryContainer"
|
||||
app:drawableStartCompat="@drawable/baseline_tag" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/explore_hashtags"
|
||||
android:textColor="?attr/colorOnSecondaryContainer"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||
android:layout_marginTop="8dp" />
|
||||
android:textColor="?attr/colorOnSecondaryContainer" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/accountsCardView"
|
||||
style="?attr/materialCardViewElevatedStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
style="?attr/materialCardViewElevatedStyle"
|
||||
app:cardBackgroundColor="?attr/colorSecondaryContainer"
|
||||
app:layout_constraintTop_toBottomOf="@id/hashtagsCardView"
|
||||
app:layout_constraintBottom_toTopOf="@id/discoverCardView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/discoverCardView">
|
||||
app:layout_constraintTop_toBottomOf="@id/hashtagsCardView">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -119,33 +122,33 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="4dp"
|
||||
android:text="@string/popular_accounts"
|
||||
android:textAppearance="?attr/textAppearanceTitleLarge"
|
||||
android:drawablePadding="4dp"
|
||||
android:textColor="?attr/colorOnSecondaryContainer"
|
||||
app:drawableStartCompat="@drawable/baseline_person_add" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/explore_accounts"
|
||||
android:textColor="?attr/colorOnSecondaryContainer"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||
android:layout_marginTop="8dp" />
|
||||
android:textColor="?attr/colorOnSecondaryContainer" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/discoverCardView"
|
||||
style="?attr/materialCardViewElevatedStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
style="?attr/materialCardViewElevatedStyle"
|
||||
app:cardBackgroundColor="?attr/colorSecondaryContainer"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountsCardView"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
app:layout_constraintTop_toBottomOf="@id/accountsCardView">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -156,22 +159,34 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="4dp"
|
||||
android:text="@string/discover"
|
||||
android:textAppearance="?attr/textAppearanceTitleLarge"
|
||||
android:drawablePadding="4dp"
|
||||
android:textColor="?attr/colorOnSecondaryContainer"
|
||||
app:drawableStartCompat="@drawable/explore_24dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/explore_posts"
|
||||
android:textColor="?attr/colorOnSecondaryContainer"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||
android:layout_marginTop="8dp" />
|
||||
android:textColor="?attr/colorOnSecondaryContainer" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:orientation="horizontal"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/search" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
50
app/src/main/res/layout/story_carousel.xml
Normal file
50
app/src/main/res/layout/story_carousel.xml
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.carousel.MaskableFrameLayout
|
||||
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:id="@+id/carousel_item_container"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout_height="240dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
app:shapeAppearance="?attr/shapeAppearanceCornerExtraLarge">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/carousel_image_view"
|
||||
android:contentDescription="@string/story_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
tools:srcCompat="@tools:sample/backgrounds/scenic" />
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/storyAuthorProfilePicture"
|
||||
android:contentDescription="@string/profile_picture"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toTopOf="@id/username"
|
||||
app:layout_constraintStart_toStartOf="@id/username"
|
||||
tools:srcCompat="@tools:sample/avatars[3]" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:textColor="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:text="pixeldroid" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.carousel.MaskableFrameLayout>
|
@ -332,4 +332,8 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
||||
<string name="summary_always_show_nsfw">NSFW/CW posts will not be blurred, and will be shown by default.</string>
|
||||
<string name="story_image">Story image</string>
|
||||
<string name="replyToStory">Reply to %1$s</string>
|
||||
<string name="storyProgress">%1$s / %2$s</string>
|
||||
<string name="story_reply_error">Something went wrong sending reply</string>
|
||||
<string name="error_fetch_story">Something went wrong fetching the carousel</string>
|
||||
<string name="sent_reply_story">Sent reply</string>
|
||||
</resources>
|
||||
|
7949
gradle/verification-metadata.xml
Normal file
7949
gradle/verification-metadata.xml
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user