mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-08-02 22:30:40 +00:00
Improved import flow, Empty pager view support, No subscriptions result view, LoginRequiredException support
This commit is contained in:
parent
f649d62e38
commit
53525cb365
20 changed files with 295 additions and 24 deletions
|
@ -71,6 +71,11 @@ class ScriptException extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class ScriptLoginRequiredException extends ScriptException {
|
||||||
|
constructor(msg) {
|
||||||
|
super("ScriptLoginRequiredException", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
class CaptchaRequiredException extends Error {
|
class CaptchaRequiredException extends Error {
|
||||||
constructor(url, body) {
|
constructor(url, body) {
|
||||||
super(JSON.stringify({ 'plugin_type': 'CaptchaRequiredException', url, body }));
|
super(JSON.stringify({ 'plugin_type': 'CaptchaRequiredException', url, body }));
|
||||||
|
|
|
@ -109,6 +109,10 @@ class UISlideOverlays {
|
||||||
menu.onOK.subscribe {
|
menu.onOK.subscribe {
|
||||||
subscription.save();
|
subscription.save();
|
||||||
menu.hide(true);
|
menu.hide(true);
|
||||||
|
|
||||||
|
if(subscription.doNotifications && !originalNotif && Settings.instance.subscriptions.subscriptionsBackgroundUpdateInterval == 0) {
|
||||||
|
UIDialogs.toast(container.context, "Enable 'Background Update' in settings for notifications to work");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
menu.onCancel.subscribe {
|
menu.onCancel.subscribe {
|
||||||
subscription.doNotifications = originalNotif;
|
subscription.doNotifications = originalNotif;
|
||||||
|
|
|
@ -34,6 +34,7 @@ import com.futo.platformplayer.engine.exceptions.PluginEngineException
|
||||||
import com.futo.platformplayer.engine.exceptions.PluginEngineStoppedException
|
import com.futo.platformplayer.engine.exceptions.PluginEngineStoppedException
|
||||||
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
|
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
|
||||||
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
||||||
|
import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException
|
||||||
import com.futo.platformplayer.engine.exceptions.ScriptValidationException
|
import com.futo.platformplayer.engine.exceptions.ScriptValidationException
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.models.ImageVariable
|
import com.futo.platformplayer.models.ImageVariable
|
||||||
|
|
|
@ -301,6 +301,7 @@ class V8Plugin {
|
||||||
"CriticalException" -> throw ScriptCriticalException(config, msg, innerEx, stack, code);
|
"CriticalException" -> throw ScriptCriticalException(config, msg, innerEx, stack, code);
|
||||||
"AgeException" -> throw ScriptAgeException(config, msg, innerEx, stack, code);
|
"AgeException" -> throw ScriptAgeException(config, msg, innerEx, stack, code);
|
||||||
"UnavailableException" -> throw ScriptUnavailableException(config, msg, innerEx, stack, code);
|
"UnavailableException" -> throw ScriptUnavailableException(config, msg, innerEx, stack, code);
|
||||||
|
"ScriptLoginRequiredException" -> throw ScriptLoginRequiredException(config, msg, innerEx, stack, code);
|
||||||
"ScriptExecutionException" -> throw ScriptExecutionException(config, msg, innerEx, stack, code);
|
"ScriptExecutionException" -> throw ScriptExecutionException(config, msg, innerEx, stack, code);
|
||||||
"ScriptCompilationException" -> throw ScriptCompilationException(config, msg, innerEx, code);
|
"ScriptCompilationException" -> throw ScriptCompilationException(config, msg, innerEx, code);
|
||||||
"ScriptImplementationException" -> throw ScriptImplementationException(config, msg, innerEx, null, code);
|
"ScriptImplementationException" -> throw ScriptImplementationException(config, msg, innerEx, null, code);
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.futo.platformplayer.engine.exceptions
|
||||||
|
|
||||||
|
import com.caoccao.javet.values.reference.V8ValueObject
|
||||||
|
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||||
|
import com.futo.platformplayer.getOrThrow
|
||||||
|
|
||||||
|
class ScriptLoginRequiredException(config: IV8PluginConfig, error: String, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptException(config, error, ex, stack, code) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException {
|
||||||
|
return ScriptLoginRequiredException(config, obj.getOrThrow(config, "message", "ScriptLoginRequiredException"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -78,7 +78,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
||||||
private var _moreButtons = arrayListOf<MenuButton>();
|
private var _moreButtons = arrayListOf<MenuButton>();
|
||||||
|
|
||||||
private var _buttonsVisible = 0;
|
private var _buttonsVisible = 0;
|
||||||
private var _subscriptionsVisible = false;
|
private var _subscriptionsVisible = true;
|
||||||
|
|
||||||
var currentButtonDefinitions: List<ButtonDefinition>? = null;
|
var currentButtonDefinitions: List<ButtonDefinition>? = null;
|
||||||
|
|
||||||
|
@ -261,11 +261,12 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerUpdateButtonEvents() {
|
private fun registerUpdateButtonEvents() {
|
||||||
|
/*
|
||||||
_subscriptionsVisible = StateSubscriptions.instance.getSubscriptionCount() > 0;
|
_subscriptionsVisible = StateSubscriptions.instance.getSubscriptionCount() > 0;
|
||||||
StateSubscriptions.instance.onSubscriptionsChanged.subscribe(this) { subs, _ ->
|
StateSubscriptions.instance.onSubscriptionsChanged.subscribe(this) { subs, _ ->
|
||||||
_subscriptionsVisible = subs.isNotEmpty();
|
_subscriptionsVisible = subs.isNotEmpty();
|
||||||
updateButtonDefinitions()
|
updateButtonDefinitions()
|
||||||
}
|
}*/
|
||||||
|
|
||||||
StatePayment.instance.hasPaidChanged.subscribe(this) {
|
StatePayment.instance.hasPaidChanged.subscribe(this) {
|
||||||
_fragment.lifecycleScope.launch(Dispatchers.Main) {
|
_fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
|
|
@ -38,6 +38,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||||
private val _containerSortBy: LinearLayout;
|
private val _containerSortBy: LinearLayout;
|
||||||
private val _tagsView: TagsView;
|
private val _tagsView: TagsView;
|
||||||
private val _textCentered: TextView;
|
private val _textCentered: TextView;
|
||||||
|
private val _emptyPagerContainer: FrameLayout;
|
||||||
|
|
||||||
protected val _toolbarContentView: LinearLayout;
|
protected val _toolbarContentView: LinearLayout;
|
||||||
|
|
||||||
|
@ -69,6 +70,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||||
inflater.inflate(R.layout.fragment_feed, this);
|
inflater.inflate(R.layout.fragment_feed, this);
|
||||||
|
|
||||||
_textCentered = findViewById(R.id.text_centered);
|
_textCentered = findViewById(R.id.text_centered);
|
||||||
|
_emptyPagerContainer = findViewById(R.id.empty_pager_container);
|
||||||
_progress_bar = findViewById(R.id.progress_bar);
|
_progress_bar = findViewById(R.id.progress_bar);
|
||||||
_progress_bar.inactiveColor = Color.TRANSPARENT;
|
_progress_bar.inactiveColor = Color.TRANSPARENT;
|
||||||
|
|
||||||
|
@ -199,6 +201,30 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||||
protected fun setTextCentered(text: String?) {
|
protected fun setTextCentered(text: String?) {
|
||||||
_textCentered.text = text;
|
_textCentered.text = text;
|
||||||
}
|
}
|
||||||
|
protected open fun getEmptyPagerView(): View? {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setEmptyPager(enable: Boolean) {
|
||||||
|
if(enable) {
|
||||||
|
val viewToShow = getEmptyPagerView();
|
||||||
|
if(viewToShow != null) {
|
||||||
|
_emptyPagerContainer.removeAllViews();
|
||||||
|
_emptyPagerContainer.addView(viewToShow);
|
||||||
|
_emptyPagerContainer.visibility = VISIBLE;
|
||||||
|
setTextCentered(null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setTextCentered(context.getString(R.string.no_results_found_swipe_down_to_refresh));
|
||||||
|
_emptyPagerContainer.visibility = GONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setTextCentered(null);
|
||||||
|
_emptyPagerContainer.removeAllViews();
|
||||||
|
_emptyPagerContainer.visibility = GONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onResume() {
|
fun onResume() {
|
||||||
//Reload the pager if the plugin was killed
|
//Reload the pager if the plugin was killed
|
||||||
|
|
|
@ -22,6 +22,10 @@ import com.futo.platformplayer.views.adapters.viewholders.SelectableIPlatformCha
|
||||||
import com.futo.platformplayer.states.StateSubscriptions
|
import com.futo.platformplayer.states.StateSubscriptions
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.states.StatePlatform
|
import com.futo.platformplayer.states.StatePlatform
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class ImportSubscriptionsFragment : MainFragment() {
|
class ImportSubscriptionsFragment : MainFragment() {
|
||||||
override val isMainView : Boolean = true;
|
override val isMainView : Boolean = true;
|
||||||
|
@ -59,11 +63,15 @@ class ImportSubscriptionsFragment : MainFragment() {
|
||||||
class ImportSubscriptionsView : LinearLayout {
|
class ImportSubscriptionsView : LinearLayout {
|
||||||
private val _fragment: ImportSubscriptionsFragment;
|
private val _fragment: ImportSubscriptionsFragment;
|
||||||
|
|
||||||
|
private val SLOWDOWN_COUNT = 100;
|
||||||
|
private val SLOWDOWN_MS: Long = 1000;
|
||||||
|
|
||||||
private var _spinner: ImageView;
|
private var _spinner: ImageView;
|
||||||
private var _textSelectDeselectAll: TextView;
|
private var _textSelectDeselectAll: TextView;
|
||||||
private var _textNothingToImport: TextView;
|
private var _textNothingToImport: TextView;
|
||||||
private var _textCounter: TextView;
|
private var _textCounter: TextView;
|
||||||
private var _textLoadMore: TextView;
|
private var _loadProgress: TextView;
|
||||||
|
//private var _textLoadMore: TextView;
|
||||||
private var _adapterView: AnyAdapterView<SelectableIPlatformChannel, ImportSubscriptionViewHolder>;
|
private var _adapterView: AnyAdapterView<SelectableIPlatformChannel, ImportSubscriptionViewHolder>;
|
||||||
private var _links: List<String> = listOf();
|
private var _links: List<String> = listOf();
|
||||||
private val _items: ArrayList<SelectableIPlatformChannel> = arrayListOf();
|
private val _items: ArrayList<SelectableIPlatformChannel> = arrayListOf();
|
||||||
|
@ -80,8 +88,9 @@ class ImportSubscriptionsFragment : MainFragment() {
|
||||||
_textNothingToImport = findViewById(R.id.nothing_to_import);
|
_textNothingToImport = findViewById(R.id.nothing_to_import);
|
||||||
_textSelectDeselectAll = findViewById(R.id.text_select_deselect_all);
|
_textSelectDeselectAll = findViewById(R.id.text_select_deselect_all);
|
||||||
_textCounter = findViewById(R.id.text_select_counter);
|
_textCounter = findViewById(R.id.text_select_counter);
|
||||||
_textLoadMore = findViewById(R.id.text_load_more);
|
//_textLoadMore = findViewById(R.id.text_load_more);
|
||||||
_spinner = findViewById(R.id.channel_loader);
|
_spinner = findViewById(R.id.channel_loader);
|
||||||
|
_loadProgress = findViewById(R.id.text_load_progress);
|
||||||
|
|
||||||
_adapterView = findViewById<RecyclerView>(R.id.recycler_import).asAny( _items) {
|
_adapterView = findViewById<RecyclerView>(R.id.recycler_import).asAny( _items) {
|
||||||
it.onSelectedChange.subscribe { c ->
|
it.onSelectedChange.subscribe { c ->
|
||||||
|
@ -113,6 +122,7 @@ class ImportSubscriptionsFragment : MainFragment() {
|
||||||
return@TaskHandler channel;
|
return@TaskHandler channel;
|
||||||
}).success {
|
}).success {
|
||||||
_items.add(SelectableIPlatformChannel(it));
|
_items.add(SelectableIPlatformChannel(it));
|
||||||
|
_loadProgress.text = "(${_items.size}/${_links.size})";
|
||||||
_adapterView.adapter.notifyItemInserted(_items.size - 1);
|
_adapterView.adapter.notifyItemInserted(_items.size - 1);
|
||||||
loadNext();
|
loadNext();
|
||||||
}.exceptionWithParameter<Throwable> { ex, para ->
|
}.exceptionWithParameter<Throwable> { ex, para ->
|
||||||
|
@ -123,6 +133,7 @@ class ImportSubscriptionsFragment : MainFragment() {
|
||||||
loadNext();
|
loadNext();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
_textLoadMore.setOnClickListener {
|
_textLoadMore.setOnClickListener {
|
||||||
if (!_limitToastShown) {
|
if (!_limitToastShown) {
|
||||||
return@setOnClickListener;
|
return@setOnClickListener;
|
||||||
|
@ -134,7 +145,7 @@ class ImportSubscriptionsFragment : MainFragment() {
|
||||||
load();
|
load();
|
||||||
};
|
};
|
||||||
|
|
||||||
_textLoadMore.visibility = View.GONE;
|
_textLoadMore.visibility = View.GONE;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
|
@ -165,12 +176,23 @@ class ImportSubscriptionsFragment : MainFragment() {
|
||||||
it.title = context.getString(R.string.import_subscriptions);
|
it.title = context.getString(R.string.import_subscriptions);
|
||||||
it.onImport.subscribe(this) {
|
it.onImport.subscribe(this) {
|
||||||
val subscriptionsToImport = _items.filter { i -> i.selected }.toList();
|
val subscriptionsToImport = _items.filter { i -> i.selected }.toList();
|
||||||
for (subscriptionToImport in subscriptionsToImport) {
|
UIDialogs.showDialogProgress(context) {
|
||||||
|
it.setText("Importing subscriptions..");
|
||||||
|
_fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
for ((i, subscriptionToImport) in subscriptionsToImport.withIndex()) {
|
||||||
StateSubscriptions.instance.addSubscription(subscriptionToImport.channel);
|
StateSubscriptions.instance.addSubscription(subscriptionToImport.channel);
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
it.setProgress(i.toDouble() / subscriptionsToImport.size);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
UIDialogs.toast("${subscriptionsToImport.size} " + context.getString(R.string.subscriptions_imported));
|
UIDialogs.toast("${subscriptionsToImport.size} " + context.getString(R.string.subscriptions_imported));
|
||||||
_fragment.closeSegment();
|
_fragment.closeSegment();
|
||||||
|
it.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,7 +202,7 @@ class ImportSubscriptionsFragment : MainFragment() {
|
||||||
if (_counter >= MAXIMUM_BATCH_SIZE) {
|
if (_counter >= MAXIMUM_BATCH_SIZE) {
|
||||||
if (!_limitToastShown) {
|
if (!_limitToastShown) {
|
||||||
_limitToastShown = true;
|
_limitToastShown = true;
|
||||||
_textLoadMore.visibility = View.VISIBLE;
|
// _textLoadMore.visibility = View.VISIBLE;
|
||||||
UIDialogs.toast(context, context.getString(R.string.stopped_after_requestcount_to_avoid_rate_limit_click_load_more_to_load_more).replace("{requestCount}", MAXIMUM_BATCH_SIZE.toString()));
|
UIDialogs.toast(context, context.getString(R.string.stopped_after_requestcount_to_avoid_rate_limit_click_load_more_to_load_more).replace("{requestCount}", MAXIMUM_BATCH_SIZE.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,11 +214,25 @@ class ImportSubscriptionsFragment : MainFragment() {
|
||||||
|
|
||||||
private fun loadNext() {
|
private fun loadNext() {
|
||||||
_currentLoadIndex++;
|
_currentLoadIndex++;
|
||||||
|
|
||||||
if (_currentLoadIndex < _links.size) {
|
if (_currentLoadIndex < _links.size) {
|
||||||
load();
|
if(_currentLoadIndex >= SLOWDOWN_COUNT) {
|
||||||
} else {
|
if(_currentLoadIndex % 10 == 0) {
|
||||||
setLoading(false);
|
val estTime = (SLOWDOWN_MS * (_links.size - _currentLoadIndex)) / 1000;
|
||||||
|
UIDialogs.toast(context, "Import slowed down to prevent rate limit (Estimate ${estTime.toInt().toHumanTimeIndicator()})");
|
||||||
}
|
}
|
||||||
|
_fragment.lifecycleScope.launch(Dispatchers.Default) {
|
||||||
|
delay(SLOWDOWN_MS);
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSelected() {
|
private fun updateSelected() {
|
||||||
|
@ -216,17 +252,19 @@ class ImportSubscriptionsFragment : MainFragment() {
|
||||||
if(isLoading){
|
if(isLoading){
|
||||||
(_spinner.drawable as Animatable?)?.start();
|
(_spinner.drawable as Animatable?)?.start();
|
||||||
_spinner.visibility = View.VISIBLE;
|
_spinner.visibility = View.VISIBLE;
|
||||||
|
_loadProgress.visibility = View.VISIBLE;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
_spinner.visibility = View.GONE;
|
_spinner.visibility = View.GONE;
|
||||||
(_spinner.drawable as Animatable?)?.stop();
|
(_spinner.drawable as Animatable?)?.stop();
|
||||||
|
_loadProgress.visibility = View.GONE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TAG = "ImportSubscriptionsFragment";
|
val TAG = "ImportSubscriptionsFragment";
|
||||||
private const val MAXIMUM_BATCH_SIZE = 100;
|
private const val MAXIMUM_BATCH_SIZE = 2000;
|
||||||
fun newInstance() = ImportSubscriptionsFragment().apply {}
|
fun newInstance() = ImportSubscriptionsFragment().apply {}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,17 +9,18 @@ import android.widget.LinearLayout
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.futo.platformplayer.*
|
import com.futo.platformplayer.*
|
||||||
|
import com.futo.platformplayer.activities.MainActivity
|
||||||
import com.futo.platformplayer.api.media.models.contents.ContentType
|
import com.futo.platformplayer.api.media.models.contents.ContentType
|
||||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||||
import com.futo.platformplayer.api.media.structures.EmptyPager
|
|
||||||
import com.futo.platformplayer.api.media.structures.IPager
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
import com.futo.platformplayer.constructs.TaskHandler
|
import com.futo.platformplayer.constructs.TaskHandler
|
||||||
import com.futo.platformplayer.engine.exceptions.PluginException
|
import com.futo.platformplayer.engine.exceptions.PluginException
|
||||||
import com.futo.platformplayer.exceptions.ChannelException
|
import com.futo.platformplayer.exceptions.ChannelException
|
||||||
import com.futo.platformplayer.exceptions.RateLimitException
|
import com.futo.platformplayer.exceptions.RateLimitException
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
import com.futo.platformplayer.models.SearchType
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.states.StateCache
|
import com.futo.platformplayer.states.StateCache
|
||||||
import com.futo.platformplayer.states.StatePlatform
|
import com.futo.platformplayer.states.StatePlatform
|
||||||
|
@ -28,9 +29,11 @@ import com.futo.platformplayer.stores.FragmentedStorage
|
||||||
import com.futo.platformplayer.stores.FragmentedStorageFileJson
|
import com.futo.platformplayer.stores.FragmentedStorageFileJson
|
||||||
import com.futo.platformplayer.views.announcements.AnnouncementView
|
import com.futo.platformplayer.views.announcements.AnnouncementView
|
||||||
import com.futo.platformplayer.views.FeedStyle
|
import com.futo.platformplayer.views.FeedStyle
|
||||||
|
import com.futo.platformplayer.views.NoResultsView
|
||||||
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
|
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
|
||||||
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
|
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
|
||||||
import com.futo.platformplayer.views.adapters.InsertedViewHolder
|
import com.futo.platformplayer.views.adapters.InsertedViewHolder
|
||||||
|
import com.futo.platformplayer.views.buttons.BigButton
|
||||||
import com.futo.platformplayer.views.subscriptions.SubscriptionBar
|
import com.futo.platformplayer.views.subscriptions.SubscriptionBar
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -41,7 +44,6 @@ import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
import kotlin.time.measureTime
|
|
||||||
|
|
||||||
class SubscriptionsFeedFragment : MainFragment() {
|
class SubscriptionsFeedFragment : MainFragment() {
|
||||||
override val isMainView : Boolean = true;
|
override val isMainView : Boolean = true;
|
||||||
|
@ -321,7 +323,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
val results = cachePager.getResults();
|
val results = cachePager.getResults();
|
||||||
Logger.i(TAG, "Subscriptions show cache (${results.size})");
|
Logger.i(TAG, "Subscriptions show cache (${results.size})");
|
||||||
setTextCentered(if (results.isEmpty()) context.getString(R.string.no_results_found_swipe_down_to_refresh) else null);
|
setEmptyPager(results.isEmpty());
|
||||||
setPager(cachePager);
|
setPager(cachePager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,15 +333,14 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||||
Logger.i(TAG, "Subscriptions load");
|
Logger.i(TAG, "Subscriptions load");
|
||||||
if(recyclerData.results.size == 0) {
|
if(recyclerData.results.size == 0) {
|
||||||
loadCache();
|
loadCache();
|
||||||
} else {
|
} else
|
||||||
setTextCentered(null);
|
setEmptyPager(false);
|
||||||
}
|
|
||||||
_taskGetPager.run(withRefetch);
|
_taskGetPager.run(withRefetch);
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>) {
|
override fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>) {
|
||||||
super.onRestoreCachedData(cachedData);
|
super.onRestoreCachedData(cachedData);
|
||||||
setTextCentered(if (cachedData.results.isEmpty()) context.getString(R.string.no_results_found_swipe_down_to_refresh) else null);
|
setEmptyPager(cachedData.results.isEmpty());
|
||||||
}
|
}
|
||||||
private fun loadedResult(pager: IPager<IPlatformContent>) {
|
private fun loadedResult(pager: IPager<IPlatformContent>) {
|
||||||
Logger.i(TAG, "Subscriptions new pager loaded (${pager.getResults().size})");
|
Logger.i(TAG, "Subscriptions new pager loaded (${pager.getResults().size})");
|
||||||
|
@ -349,7 +350,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||||
finishRefreshLayoutLoader();
|
finishRefreshLayoutLoader();
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setPager(pager);
|
setPager(pager);
|
||||||
setTextCentered(if (pager.getResults().isEmpty()) context.getString(R.string.no_results_found_swipe_down_to_refresh) else null);
|
setEmptyPager(pager.getResults().isEmpty());
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Logger.e(TAG, "Failed to finish loading", e)
|
Logger.e(TAG, "Failed to finish loading", e)
|
||||||
}
|
}
|
||||||
|
@ -359,6 +360,22 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
override fun getEmptyPagerView(): View? {
|
||||||
|
val dp10 = 10.dp(resources);
|
||||||
|
val dp30 = 30.dp(resources);
|
||||||
|
if(StateSubscriptions.instance.getSubscriptions().isEmpty())
|
||||||
|
return NoResultsView(context, "You have no subscriptions", "Subscribe to some creators or import them from elsewhere.", R.drawable.ic_explore, listOf(
|
||||||
|
BigButton(context, "Search", "Search for creators in your enabled plugins", R.drawable.ic_creators) {
|
||||||
|
fragment.navigate<SuggestionsFragment>(SuggestionsFragmentData("", SearchType.CREATOR));
|
||||||
|
}.withMargin(dp10, dp30),
|
||||||
|
BigButton(context, "Import", "Import your subscriptions from another format", R.drawable.ic_move_up) {
|
||||||
|
val activity = StateApp.instance.context;
|
||||||
|
if(activity is MainActivity)
|
||||||
|
UIDialogs.showImportOptionsDialog(activity);
|
||||||
|
}.withMargin(dp10, dp30)
|
||||||
|
));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleExceptions(exs: List<Throwable>) {
|
private fun handleExceptions(exs: List<Throwable>) {
|
||||||
context?.let {
|
context?.let {
|
||||||
|
|
|
@ -51,6 +51,8 @@ import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
|
||||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||||
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
||||||
|
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||||
|
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||||
import com.futo.platformplayer.api.media.platforms.js.models.JSVideoDetails
|
import com.futo.platformplayer.api.media.platforms.js.models.JSVideoDetails
|
||||||
import com.futo.platformplayer.api.media.structures.IPager
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
import com.futo.platformplayer.casting.CastConnectionState
|
import com.futo.platformplayer.casting.CastConnectionState
|
||||||
|
@ -62,6 +64,7 @@ import com.futo.platformplayer.downloads.VideoLocal
|
||||||
import com.futo.platformplayer.engine.exceptions.ScriptAgeException
|
import com.futo.platformplayer.engine.exceptions.ScriptAgeException
|
||||||
import com.futo.platformplayer.engine.exceptions.ScriptException
|
import com.futo.platformplayer.engine.exceptions.ScriptException
|
||||||
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
||||||
|
import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException
|
||||||
import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException
|
import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException
|
||||||
import com.futo.platformplayer.exceptions.UnsupportedCastException
|
import com.futo.platformplayer.exceptions.UnsupportedCastException
|
||||||
import com.futo.platformplayer.helpers.VideoHelper
|
import com.futo.platformplayer.helpers.VideoHelper
|
||||||
|
@ -2317,6 +2320,22 @@ class VideoDetailView : ConstraintLayout {
|
||||||
StateAnnouncement.instance.registerAnnouncement(video?.id?.value + "_Q_NOSOURCES", context.getString(R.string.video_without_source), context.getString(R.string.there_was_a_in_your_queue_videoname_by_authorname_without_the_required_source_being_enabled_playback_was_skipped).replace("{videoName}", video?.name ?: "").replace("{authorName}", video?.author?.name ?: ""), AnnouncementType.SESSION)
|
StateAnnouncement.instance.registerAnnouncement(video?.id?.value + "_Q_NOSOURCES", context.getString(R.string.video_without_source), context.getString(R.string.there_was_a_in_your_queue_videoname_by_authorname_without_the_required_source_being_enabled_playback_was_skipped).replace("{videoName}", video?.name ?: "").replace("{authorName}", video?.author?.name ?: ""), AnnouncementType.SESSION)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.exception<ScriptLoginRequiredException> {
|
||||||
|
Logger.w(TAG, "exception<ScriptLoginRequiredException>", it);
|
||||||
|
|
||||||
|
UIDialogs.showDialog(context, R.drawable.ic_security, "Authentication", it.message, null, 0,
|
||||||
|
UIDialogs.Action("Cancel", {}),
|
||||||
|
UIDialogs.Action("Login", {
|
||||||
|
val id = it.config?.let { if(it is SourcePluginConfig) it.id else null };
|
||||||
|
val didLogin = if(id == null)
|
||||||
|
false
|
||||||
|
else StatePlugins.instance.loginPlugin(context, id) {
|
||||||
|
fetchVideo();
|
||||||
|
}
|
||||||
|
if(!didLogin)
|
||||||
|
UIDialogs.showDialogOk(context, R.drawable.ic_error_pred, "Failed to login");
|
||||||
|
}));
|
||||||
|
}
|
||||||
.exception<ContentNotAvailableYetException> {
|
.exception<ContentNotAvailableYetException> {
|
||||||
Logger.w(TAG, "exception<ContentNotAvailableYetException>", it)
|
Logger.w(TAG, "exception<ContentNotAvailableYetException>", it)
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ import com.futo.platformplayer.background.BackgroundWorker
|
||||||
import com.futo.platformplayer.casting.StateCasting
|
import com.futo.platformplayer.casting.StateCasting
|
||||||
import com.futo.platformplayer.constructs.Event0
|
import com.futo.platformplayer.constructs.Event0
|
||||||
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
|
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
|
||||||
|
import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException
|
||||||
import com.futo.platformplayer.fragment.mainactivity.main.HomeFragment
|
import com.futo.platformplayer.fragment.mainactivity.main.HomeFragment
|
||||||
import com.futo.platformplayer.fragment.mainactivity.main.SourceDetailFragment
|
import com.futo.platformplayer.fragment.mainactivity.main.SourceDetailFragment
|
||||||
import com.futo.platformplayer.logging.AndroidLogConsumer
|
import com.futo.platformplayer.logging.AndroidLogConsumer
|
||||||
|
@ -751,6 +752,9 @@ class StateApp {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun handleLoginException(client: JSClient, exception: ScriptLoginRequiredException, onSuccess: (client: JSClient)->Unit) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
fun getLocaleContext(baseContext: Context?): Context? {
|
fun getLocaleContext(baseContext: Context?): Context? {
|
||||||
val locale = getLocaleSetting(baseContext);
|
val locale = getLocaleSetting(baseContext);
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.futo.platformplayer.states
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.UIDialogs
|
import com.futo.platformplayer.UIDialogs
|
||||||
|
import com.futo.platformplayer.activities.LoginActivity
|
||||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||||
import com.futo.platformplayer.api.media.platforms.js.SourceAuth
|
import com.futo.platformplayer.api.media.platforms.js.SourceAuth
|
||||||
|
@ -53,6 +54,25 @@ class StatePlugins {
|
||||||
.load();
|
.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loginPlugin(context: Context, id: String, afterLogin: ()->Unit): Boolean {
|
||||||
|
val descriptor = getPlugin(id) ?: return false;
|
||||||
|
val config = descriptor.config;
|
||||||
|
|
||||||
|
if(config.authentication == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LoginActivity.showLogin(context, config) {
|
||||||
|
StatePlugins.instance.setPluginAuth(config.id, it);
|
||||||
|
|
||||||
|
StateApp.instance.scope.launch(Dispatchers.IO) {
|
||||||
|
StatePlatform.instance.reloadClient(context, id);
|
||||||
|
afterLogin.invoke();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun getResourceIdFromString(resourceName: String, c: Class<*> = R.drawable::class.java): Int? {
|
private fun getResourceIdFromString(resourceName: String, c: Class<*> = R.drawable::class.java): Int? {
|
||||||
return try {
|
return try {
|
||||||
val idField = c.getDeclaredField(resourceName)
|
val idField = c.getDeclaredField(resourceName)
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.futo.platformplayer.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import com.futo.platformplayer.R
|
||||||
|
|
||||||
|
class NoResultsView: ConstraintLayout {
|
||||||
|
|
||||||
|
val textTitle: TextView;
|
||||||
|
val textCentered: TextView;
|
||||||
|
val icon: ImageView;
|
||||||
|
val containerExtraViews: LinearLayout;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(context: Context, title: String, text: String, iconId: Int, extraViews: List<View>) : super(context) {
|
||||||
|
inflate(context, R.layout.view_no_results, this);
|
||||||
|
textTitle = findViewById(R.id.text_title)
|
||||||
|
textCentered = findViewById(R.id.text_centered);
|
||||||
|
icon = findViewById(R.id.icon);
|
||||||
|
containerExtraViews = findViewById(R.id.container_extra_views);
|
||||||
|
textTitle.text = title;
|
||||||
|
textCentered.text = text;
|
||||||
|
icon.setImageResource(iconId);
|
||||||
|
if(iconId < 0)
|
||||||
|
icon.visibility = GONE;
|
||||||
|
|
||||||
|
for(view in extraViews)
|
||||||
|
containerExtraViews.addView(view);
|
||||||
|
}
|
||||||
|
}
|
|
@ -76,6 +76,11 @@ open class BigButton : LinearLayout {
|
||||||
_textSecondary.text = attrTextSecondary;
|
_textSecondary.text = attrTextSecondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun withMargin(bottom: Int, side: Int = 0): BigButton {
|
||||||
|
setPadding(side, 0, side, bottom)
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
fun setSecondaryText(text: String?) {
|
fun setSecondaryText(text: String?) {
|
||||||
_textSecondary.text = text
|
_textSecondary.text = text
|
||||||
}
|
}
|
||||||
|
|
6
app/src/main/res/drawable/background_big_primary.xml
Normal file
6
app/src/main/res/drawable/background_big_primary.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="#142B66" />
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
|
||||||
|
</shape>
|
9
app/src/main/res/drawable/ic_explore.xml
Normal file
9
app/src/main/res/drawable/ic_explore.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M480,840Q405.46,840 339.77,811.58Q274.08,783.15 225.46,734.54Q176.85,685.92 148.42,620.23Q120,554.54 120,480Q120,405.46 148.42,339.77Q176.85,274.08 225.46,225.46Q274.08,176.85 339.77,148.42Q405.46,120 480,120Q619.85,120 721.27,212.27Q822.69,304.54 837.39,440.23L796.92,440.23Q786.39,353.38 734.19,284.35Q682,215.31 600,182.46L600,200Q600,233 576.5,256.5Q553,280 520,280L440,280L440,360Q440,377 428.5,388.5Q417,400 400,400L320,400L320,480L393.85,480L393.85,600L360,600L168,408Q165,426 162.5,444Q160,462 160,480Q160,611 252,705Q344,799 480,800L480,840ZM840.92,829.23L702.92,692.77Q685,705.54 664.08,712.77Q643.15,720 620,720Q561.15,720 520.58,679.42Q480,638.85 480,580Q480,521.15 520.58,480.58Q561.15,440 620,440Q678.85,440 719.42,480.58Q760,521.15 760,580Q760,603.92 752.39,625.23Q744.77,646.54 731.23,664.46L869.23,800.92L840.92,829.23ZM620,680Q662,680 691,651Q720,622 720,580Q720,538 691,509Q662,480 620,480Q578,480 549,509Q520,538 520,580Q520,622 549,651Q578,680 620,680Z"/>
|
||||||
|
</vector>
|
|
@ -106,6 +106,11 @@
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textColor="@color/gray_ac"
|
android:textColor="@color/gray_ac"
|
||||||
android:textSize="12dp" />
|
android:textSize="12dp" />
|
||||||
|
<FrameLayout
|
||||||
|
android:visibility="gone"
|
||||||
|
android:id="@+id/empty_pager_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,16 @@
|
||||||
<Space android:layout_width="0dp"
|
<Space android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_load_progress"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/inter_light"
|
||||||
|
android:textSize="15dp"
|
||||||
|
android:text="(0/0)"
|
||||||
|
android:textColor="#58595B" />
|
||||||
|
|
||||||
|
<!--
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_load_more"
|
android:id="@+id/text_load_more"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -47,7 +56,7 @@
|
||||||
android:fontFamily="@font/inter_light"
|
android:fontFamily="@font/inter_light"
|
||||||
android:textSize="15dp"
|
android:textSize="15dp"
|
||||||
android:text="@string/load_more"
|
android:text="@string/load_more"
|
||||||
android:textColor="@color/colorPrimary" />
|
android:textColor="@color/colorPrimary" /> -->
|
||||||
|
|
||||||
<Space android:layout_width="0dp"
|
<Space android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
53
app/src/main/res/layout/view_no_results.xml
Normal file
53
app/src/main/res/layout/view_no_results.xml
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:scaleType="fitCenter" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:fontFamily="@font/inter_bold"
|
||||||
|
android:text="No results"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="22dp" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_centered"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:fontFamily="@font/inter_regular"
|
||||||
|
android:text="No results were found for this page"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="@color/gray_ac"
|
||||||
|
android:textSize="13dp" />
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/container_extra_views"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_margin="10dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1 +1 @@
|
||||||
Subproject commit 1b08b18c74f284cdd2b1858950efe6583fbcbef0
|
Subproject commit 863d0be1322660c99e4d0cdae0b45d0a5918542d
|
Loading…
Add table
Add a link
Reference in a new issue