diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt index f2241446..33a06523 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt @@ -109,7 +109,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { @JsonProperty("updated_at") val updatedAt: String?, @JsonProperty("media_type") val mediaType: String?, @JsonProperty("status") val status: String?, - @JsonProperty("genres") val genres: ArrayList, + @JsonProperty("genres") val genres: ArrayList?, @JsonProperty("my_list_status") val myListStatus: MyListStatus?, @JsonProperty("num_episodes") val numEpisodes: Int?, @JsonProperty("start_season") val startSeason: StartSeason?, @@ -117,12 +117,12 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { @JsonProperty("source") val source: String?, @JsonProperty("average_episode_duration") val averageEpisodeDuration: Int?, @JsonProperty("rating") val rating: String?, - @JsonProperty("pictures") val pictures: ArrayList, + @JsonProperty("pictures") val pictures: ArrayList?, @JsonProperty("background") val background: String?, - @JsonProperty("related_anime") val relatedAnime: ArrayList, - @JsonProperty("related_manga") val relatedManga: ArrayList, - @JsonProperty("recommendations") val recommendations: ArrayList, - @JsonProperty("studios") val studios: ArrayList, + @JsonProperty("related_anime") val relatedAnime: ArrayList?, + @JsonProperty("related_manga") val relatedManga: ArrayList?, + @JsonProperty("recommendations") val recommendations: ArrayList?, + @JsonProperty("studios") val studios: ArrayList?, @JsonProperty("statistics") val statistics: Statistics?, ) @@ -136,7 +136,6 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { @JsonProperty("name") val name: String? = null ) - data class MyListStatus( @JsonProperty("status") val status: String? = null, @JsonProperty("score") val score: Int? = null, @@ -172,7 +171,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { } } - private fun toSearchResult(node : Node?) : SyncAPI.SyncSearchResult? { + private fun toSearchResult(node: Node?): SyncAPI.SyncSearchResult? { return SyncAPI.SyncSearchResult( name = node?.title ?: return null, syncApiName = this.name, @@ -205,19 +204,19 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { else -> null }, nextAiring = null, - studio = malAnime.studios.mapNotNull { it.name }, - genres = malAnime.genres.map { it.name }, + studio = malAnime.studios?.mapNotNull { it.name }, + genres = malAnime.genres?.map { it.name }, trailerUrl = null, startDate = parseDate(malAnime.startDate), endDate = parseDate(malAnime.endDate), - recommendations = malAnime.recommendations.mapNotNull { rec -> + recommendations = malAnime.recommendations?.mapNotNull { rec -> val node = rec.node ?: return@mapNotNull null toSearchResult(node) }, - nextSeason = malAnime.relatedAnime.firstOrNull { + nextSeason = malAnime.relatedAnime?.firstOrNull { return@firstOrNull it.relationType == "sequel" - }?.let { toSearchResult(it.node) }, - prevSeason = malAnime.relatedAnime.firstOrNull { + }?.let { toSearchResult(it.node) }, + prevSeason = malAnime.relatedAnime?.firstOrNull { return@firstOrNull it.relationType == "prequel" }?.let { toSearchResult(it.node) }, actors = null, @@ -229,7 +228,8 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { override suspend fun getStatus(id: String): SyncAPI.SyncStatus? { val internalId = id.toIntOrNull() ?: return null - val data = getDataAboutMalId(internalId)?.my_list_status //?: throw ErrorLoadingException("No my_list_status") + val data = + getDataAboutMalId(internalId)?.my_list_status //?: throw ErrorLoadingException("No my_list_status") return SyncAPI.SyncStatus( score = data?.score, status = malStatusAsString.indexOf(data?.status), diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index c4e84cd4..244af24a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -11,15 +11,15 @@ import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Rect import android.net.Uri +import android.os.Build import android.os.Bundle +import android.text.Editable import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView -import android.widget.Toast +import android.widget.* import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.core.content.FileProvider @@ -27,6 +27,7 @@ import androidx.core.graphics.drawable.toBitmap import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.widget.NestedScrollView +import androidx.core.widget.doOnTextChanged import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.preference.PreferenceManager @@ -1149,7 +1150,79 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio } } - observe(syncModel.status) { status -> + context?.let { ctx -> + val arrayAdapter = ArrayAdapter(ctx, R.layout.sort_bottom_single_choice) + /* + -1 -> None + 0 -> Watching + 1 -> Completed + 2 -> OnHold + 3 -> Dropped + 4 -> PlanToWatch + 5 -> ReWatching + */ + val items = listOf( + R.string.none, + R.string.type_watching, + R.string.type_completed, + R.string.type_on_hold, + R.string.type_dropped, + R.string.type_plan_to_watch, + R.string.type_re_watching + ).map { ctx.getString(it) } + arrayAdapter.addAll(items) + result_sync_check?.choiceMode = AbsListView.CHOICE_MODE_SINGLE + result_sync_check?.adapter = arrayAdapter + UIHelper.setListViewHeightBasedOnItems(result_sync_check) + + result_sync_check?.setOnItemClickListener { _, _, which, _ -> + syncModel.setStatus(which - 1) + } + + result_sync_rating?.addOnChangeListener { _, value, _ -> + syncModel.setScore(value.toInt()) + } + + result_sync_add_episode?.setOnClickListener { + syncModel.setEpisodesDelta(1) + } + + result_sync_sub_episode?.setOnClickListener { + syncModel.setEpisodesDelta(-1) + } + + result_sync_current_episodes?.doOnTextChanged { text, start, before, count -> + if(count == before) return@doOnTextChanged + text?.toString()?.toIntOrNull()?.let { ep -> + syncModel.setEpisodes(ep) + } + } + } + + observe(syncModel.metadata) { meta -> + when (meta) { + is Resource.Success -> { + val d = meta.value + result_sync_episodes?.max = (d.totalEpisodes ?: 0)*1000 + normalSafeApiCall { + val ctx = result_sync_max_episodes?.context + result_sync_max_episodes?.text = + d.totalEpisodes?.let { + ctx?.getString(R.string.sync_total_episodes_some)?.format(it) + } ?: run { + ctx?.getString(R.string.sync_total_episodes_none) + } + } + } + is Resource.Loading -> { + result_sync_max_episodes?.text = + result_sync_max_episodes?.context?.getString(R.string.sync_total_episodes_none) + } + else -> {} + } + } + + observe(syncModel.userData) { status -> var closed = false when (status) { is Resource.Failure -> { @@ -1169,17 +1242,20 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio val d = status.value result_sync_rating?.value = d.score?.toFloat() ?: 0.0f - - /*when(d.status) { - -1 -> None - 0 -> Watching - 1 -> Completed - 2 -> OnHold - 3 -> Dropped - 4 -> PlanToWatch - 5 -> ReWatching - }*/ - //d.status + result_sync_check?.setItemChecked(d.status + 1, true) + val watchedEpisodes = d.watchedEpisodes ?: 0 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + result_sync_episodes?.setProgress(watchedEpisodes * 1000, true) + } else { + result_sync_episodes?.progress = watchedEpisodes * 1000 + } + result_sync_current_episodes?.text = + Editable.Factory.getInstance()?.newEditable(watchedEpisodes.toString()) + normalSafeApiCall { // format might fail + context?.getString(R.string.sync_score_format)?.format(d.score ?: 0)?.let { + result_sync_score_text?.text = it + } + } } null -> { closed = false @@ -1349,10 +1425,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio } result_sync_set_score?.setOnClickListener { - // TODO set score - //syncModel.setScore(SyncAPI.SyncStatus( - // status = - //)) + syncModel.publishUserData() } observe(viewModel.publicEpisodesCount) { count -> @@ -1455,7 +1528,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio setAniListSync(d.anilistId?.toString()) ) { syncModel.updateMetadata() - syncModel.updateStatus() + syncModel.updateUserData() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt index 3b4cabde..c2e13cf1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt @@ -11,6 +11,7 @@ import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.malApi import com.lagradost.cloudstream3.syncproviders.SyncAPI import kotlinx.coroutines.launch + class SyncViewModel : ViewModel() { private val repos = SyncApis @@ -19,10 +20,10 @@ class SyncViewModel : ViewModel() { val metadata: LiveData> get() = _metaResponse - private val _statusResponse: MutableLiveData?> = + private val _userDataResponse: MutableLiveData?> = MutableLiveData(null) - val status: LiveData?> get() = _statusResponse + val userData: LiveData?> get() = _userDataResponse // prefix, id private val syncIds = hashMapOf() @@ -35,29 +36,75 @@ class SyncViewModel : ViewModel() { syncIds[aniListApi.idPrefix] = id } - fun setScore(status: SyncAPI.SyncStatus) = viewModelScope.launch { - for ((prefix, id) in syncIds) { - repos.firstOrNull { it.idPrefix == prefix }?.score(id, status) + fun setEpisodesDelta(delta: Int) { + val user = userData.value + if (user is Resource.Success) { + user.value.watchedEpisodes?.plus( + delta + )?.let { episode -> + setEpisodes(episode) + } } - - updateStatus() } - fun updateStatus() = viewModelScope.launch { - _statusResponse.postValue(Resource.Loading()) + fun setEpisodes(episodes: Int) { + if (episodes < 0) return + val meta = metadata.value + if (meta is Resource.Success) { + meta.value.totalEpisodes?.let { max -> + if (episodes > max) { + setEpisodes(max) + return + } + } + } + + val user = userData.value + if (user is Resource.Success) { + _userDataResponse.postValue(Resource.Success(user.value.copy(watchedEpisodes = episodes))) + } + } + + fun setScore(score: Int) { + val user = userData.value + if (user is Resource.Success) { + _userDataResponse.postValue(Resource.Success(user.value.copy(score = score))) + } + } + + fun setStatus(which: Int) { + if (which < -1 || which > 5) return // validate input + val user = userData.value + if (user is Resource.Success) { + _userDataResponse.postValue(Resource.Success(user.value.copy(status = which))) + } + } + + fun publishUserData() = viewModelScope.launch { + val user = userData.value + if (user is Resource.Success) { + for ((prefix, id) in syncIds) { + repos.firstOrNull { it.idPrefix == prefix }?.score(id, user.value) + } + } + updateUserData() + } + + fun updateUserData() = viewModelScope.launch { + _userDataResponse.postValue(Resource.Loading()) var lastError: Resource = Resource.Failure(false, null, null, "No data") for ((prefix, id) in syncIds) { repos.firstOrNull { it.idPrefix == prefix }?.let { val result = it.getStatus(id) if (result is Resource.Success) { - _statusResponse.postValue(result) + _userDataResponse.postValue(result) return@launch } else if (result is Resource.Failure) { lastError = result } } } - _statusResponse.postValue(lastError) + _userDataResponse.postValue(lastError) } fun updateMetadata() = viewModelScope.launch { @@ -75,5 +122,6 @@ class SyncViewModel : ViewModel() { } } _metaResponse.postValue(lastError) + setEpisodesDelta(0) } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 8a2a6443..1609e258 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -88,7 +88,7 @@ class SettingsFragment : PreferenceFragmentCompat() { return uiModeManager?.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION } - const val accountEnabled = false + const val accountEnabled = true } private var beneneCount = 0 diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index aedbe2e8..0afeabb0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -12,12 +12,11 @@ import android.content.res.Resources import android.graphics.Color import android.os.Build import android.os.Bundle -import android.view.Gravity -import android.view.MenuItem -import android.view.View -import android.view.WindowManager +import android.view.* import android.view.inputmethod.InputMethodManager import android.widget.ImageView +import android.widget.ListAdapter +import android.widget.ListView import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.annotation.IdRes @@ -71,6 +70,36 @@ object UIHelper { ) } + + /** + * Sets ListView height dynamically based on the height of the items. + * + * @param listView to be resized + * @return true if the listView is successfully resized, false otherwise + */ + fun setListViewHeightBasedOnItems(listView: ListView?) { + val listAdapter: ListAdapter = listView?.adapter ?: return + val numberOfItems: Int = listAdapter.count + + // Get total height of all items. + var totalItemsHeight = 0 + for (itemPos in 0 until numberOfItems) { + val item: View = listAdapter.getView(itemPos, null, listView) + item.measure(0, 0) + totalItemsHeight += item.measuredHeight + } + + // Get total height of all item dividers. + val totalDividersHeight: Int = listView.dividerHeight * + (numberOfItems - 1) + + // Set list height. + val params: ViewGroup.LayoutParams = listView.layoutParams + params.height = totalItemsHeight + totalDividersHeight + listView.layoutParams = params + listView.requestLayout() + } + fun Activity?.getSpanCount(): Int? { val compactView = this?.getGridIsCompact() ?: return null val spanCountLandscape = if (compactView) 2 else 6 @@ -120,7 +149,7 @@ object UIHelper { return color } - fun ImageView?.setImage(url: String?) : Boolean { + fun ImageView?.setImage(url: String?): Boolean { if (this == null || url.isNullOrBlank()) return false return try { GlideApp.with(this.context) @@ -316,7 +345,7 @@ object UIHelper { fun Activity.showSystemUI() { window.decorView.systemUiVisibility = - (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) + (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) changeStatusBarState(isEmulatorSettings()) diff --git a/app/src/main/res/layout/result_sync.xml b/app/src/main/res/layout/result_sync.xml index b21846ce..ee41f563 100644 --- a/app/src/main/res/layout/result_sync.xml +++ b/app/src/main/res/layout/result_sync.xml @@ -6,363 +6,265 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - + android:layout_height="wrap_content"> - - - - - - - - - - - - - - - - + + + + + + + + + --> + android:layout_height="wrap_content" /> + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + android:layout_height="30dp" /> + + + android:layout_height="30dp" /> + + + + android:layout_height="30dp" /> + + + + + + + android:layout_height="30dp" /> + + - \ No newline at end of file diff --git a/app/src/main/res/layout/sort_bottom_footer_add_choice.xml b/app/src/main/res/layout/sort_bottom_footer_add_choice.xml index 6da8f7db..a2c7ed0f 100644 --- a/app/src/main/res/layout/sort_bottom_footer_add_choice.xml +++ b/app/src/main/res/layout/sort_bottom_footer_add_choice.xml @@ -1,23 +1,7 @@ \ No newline at end of file diff --git a/app/src/main/res/layout/sort_bottom_single_choice.xml b/app/src/main/res/layout/sort_bottom_single_choice.xml index a3291d77..6e81cf2e 100644 --- a/app/src/main/res/layout/sort_bottom_single_choice.xml +++ b/app/src/main/res/layout/sort_bottom_single_choice.xml @@ -14,25 +14,7 @@ --> + style="@style/CheckLabel" + tools:text="hello" + android:id="@android:id/text1" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5bb5320c..d3f9d6dc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -395,7 +395,10 @@ Add tracking Added %s Sync - + Rated + %d / 10 + /?? + /%d None Normal diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 66d5a3d8..f6a68274 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -370,6 +370,24 @@ ?attr/textColor + +