diff --git a/app/src/main/assets/scripts/source.js b/app/src/main/assets/scripts/source.js index 87b3a609..b5e0f100 100644 --- a/app/src/main/assets/scripts/source.js +++ b/app/src/main/assets/scripts/source.js @@ -71,6 +71,11 @@ class ScriptException extends Error { } } } +class ScriptLoginRequiredException extends ScriptException { + constructor(msg) { + super("ScriptLoginRequiredException", msg); + } +} class CaptchaRequiredException extends Error { constructor(url, body) { super(JSON.stringify({ 'plugin_type': 'CaptchaRequiredException', url, body })); diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index 80f33705..ccb083a0 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -109,6 +109,10 @@ class UISlideOverlays { menu.onOK.subscribe { subscription.save(); 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 { subscription.doNotifications = originalNotif; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt index 9b525fe6..b3450002 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -34,6 +34,7 @@ import com.futo.platformplayer.engine.exceptions.PluginEngineException import com.futo.platformplayer.engine.exceptions.PluginEngineStoppedException import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException 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.logging.Logger import com.futo.platformplayer.models.ImageVariable diff --git a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt index a2caf747..65155ac3 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt @@ -301,6 +301,7 @@ class V8Plugin { "CriticalException" -> throw ScriptCriticalException(config, msg, innerEx, stack, code); "AgeException" -> throw ScriptAgeException(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); "ScriptCompilationException" -> throw ScriptCompilationException(config, msg, innerEx, code); "ScriptImplementationException" -> throw ScriptImplementationException(config, msg, innerEx, null, code); diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptLoginRequiredException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptLoginRequiredException.kt new file mode 100644 index 00000000..423d5786 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptLoginRequiredException.kt @@ -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")); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt index f2e420b9..e96754ce 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt @@ -78,7 +78,7 @@ class MenuBottomBarFragment : MainActivityFragment() { private var _moreButtons = arrayListOf(); private var _buttonsVisible = 0; - private var _subscriptionsVisible = false; + private var _subscriptionsVisible = true; var currentButtonDefinitions: List? = null; @@ -261,11 +261,12 @@ class MenuBottomBarFragment : MainActivityFragment() { } private fun registerUpdateButtonEvents() { + /* _subscriptionsVisible = StateSubscriptions.instance.getSubscriptionCount() > 0; StateSubscriptions.instance.onSubscriptionsChanged.subscribe(this) { subs, _ -> _subscriptionsVisible = subs.isNotEmpty(); updateButtonDefinitions() - } + }*/ StatePayment.instance.hasPaidChanged.subscribe(this) { _fragment.lifecycleScope.launch(Dispatchers.Main) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt index 83db81d7..78459af4 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt @@ -38,6 +38,7 @@ abstract class FeedView : L private val _containerSortBy: LinearLayout; private val _tagsView: TagsView; private val _textCentered: TextView; + private val _emptyPagerContainer: FrameLayout; protected val _toolbarContentView: LinearLayout; @@ -69,6 +70,7 @@ abstract class FeedView : L inflater.inflate(R.layout.fragment_feed, this); _textCentered = findViewById(R.id.text_centered); + _emptyPagerContainer = findViewById(R.id.empty_pager_container); _progress_bar = findViewById(R.id.progress_bar); _progress_bar.inactiveColor = Color.TRANSPARENT; @@ -199,6 +201,30 @@ abstract class FeedView : L protected fun setTextCentered(text: String?) { _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() { //Reload the pager if the plugin was killed diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportSubscriptionsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportSubscriptionsFragment.kt index 2f3c93f6..e245ec74 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportSubscriptionsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportSubscriptionsFragment.kt @@ -22,6 +22,10 @@ import com.futo.platformplayer.views.adapters.viewholders.SelectableIPlatformCha import com.futo.platformplayer.states.StateSubscriptions import com.futo.platformplayer.logging.Logger 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() { override val isMainView : Boolean = true; @@ -59,11 +63,15 @@ class ImportSubscriptionsFragment : MainFragment() { class ImportSubscriptionsView : LinearLayout { private val _fragment: ImportSubscriptionsFragment; + private val SLOWDOWN_COUNT = 100; + private val SLOWDOWN_MS: Long = 1000; + private var _spinner: ImageView; private var _textSelectDeselectAll: TextView; private var _textNothingToImport: TextView; private var _textCounter: TextView; - private var _textLoadMore: TextView; + private var _loadProgress: TextView; + //private var _textLoadMore: TextView; private var _adapterView: AnyAdapterView; private var _links: List = listOf(); private val _items: ArrayList = arrayListOf(); @@ -80,8 +88,9 @@ class ImportSubscriptionsFragment : MainFragment() { _textNothingToImport = findViewById(R.id.nothing_to_import); _textSelectDeselectAll = findViewById(R.id.text_select_deselect_all); _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); + _loadProgress = findViewById(R.id.text_load_progress); _adapterView = findViewById(R.id.recycler_import).asAny( _items) { it.onSelectedChange.subscribe { c -> @@ -113,6 +122,7 @@ class ImportSubscriptionsFragment : MainFragment() { return@TaskHandler channel; }).success { _items.add(SelectableIPlatformChannel(it)); + _loadProgress.text = "(${_items.size}/${_links.size})"; _adapterView.adapter.notifyItemInserted(_items.size - 1); loadNext(); }.exceptionWithParameter { ex, para -> @@ -123,6 +133,7 @@ class ImportSubscriptionsFragment : MainFragment() { loadNext(); }; + /* _textLoadMore.setOnClickListener { if (!_limitToastShown) { return@setOnClickListener; @@ -134,7 +145,7 @@ class ImportSubscriptionsFragment : MainFragment() { load(); }; - _textLoadMore.visibility = View.GONE; + _textLoadMore.visibility = View.GONE;*/ } fun cleanup() { @@ -165,12 +176,23 @@ class ImportSubscriptionsFragment : MainFragment() { it.title = context.getString(R.string.import_subscriptions); it.onImport.subscribe(this) { val subscriptionsToImport = _items.filter { i -> i.selected }.toList(); - for (subscriptionToImport in subscriptionsToImport) { - StateSubscriptions.instance.addSubscription(subscriptionToImport.channel); + UIDialogs.showDialogProgress(context) { + it.setText("Importing subscriptions.."); + _fragment.lifecycleScope.launch(Dispatchers.IO) { + for ((i, subscriptionToImport) in subscriptionsToImport.withIndex()) { + 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)); + _fragment.closeSegment(); + it.dismiss(); + } + } } - UIDialogs.toast("${subscriptionsToImport.size} " + context.getString(R.string.subscriptions_imported)); - _fragment.closeSegment(); }; } } @@ -180,7 +202,7 @@ class ImportSubscriptionsFragment : MainFragment() { if (_counter >= MAXIMUM_BATCH_SIZE) { if (!_limitToastShown) { _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())); } @@ -192,11 +214,25 @@ class ImportSubscriptionsFragment : MainFragment() { private fun loadNext() { _currentLoadIndex++; + if (_currentLoadIndex < _links.size) { - load(); - } else { - setLoading(false); + if(_currentLoadIndex >= SLOWDOWN_COUNT) { + if(_currentLoadIndex % 10 == 0) { + 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() { @@ -216,17 +252,19 @@ class ImportSubscriptionsFragment : MainFragment() { if(isLoading){ (_spinner.drawable as Animatable?)?.start(); _spinner.visibility = View.VISIBLE; + _loadProgress.visibility = View.VISIBLE; } else { _spinner.visibility = View.GONE; (_spinner.drawable as Animatable?)?.stop(); + _loadProgress.visibility = View.GONE; } } } companion object { val TAG = "ImportSubscriptionsFragment"; - private const val MAXIMUM_BATCH_SIZE = 100; + private const val MAXIMUM_BATCH_SIZE = 2000; fun newInstance() = ImportSubscriptionsFragment().apply {} } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt index c1681a8f..fd821356 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt @@ -9,17 +9,18 @@ import android.widget.LinearLayout import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager 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.IPlatformContent import com.futo.platformplayer.api.media.models.video.IPlatformVideo 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.constructs.TaskHandler import com.futo.platformplayer.engine.exceptions.PluginException import com.futo.platformplayer.exceptions.ChannelException import com.futo.platformplayer.exceptions.RateLimitException import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.SearchType import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateCache 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.views.announcements.AnnouncementView 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.InsertedViewAdapterWithLoader import com.futo.platformplayer.views.adapters.InsertedViewHolder +import com.futo.platformplayer.views.buttons.BigButton import com.futo.platformplayer.views.subscriptions.SubscriptionBar import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers @@ -41,7 +44,6 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.time.OffsetDateTime import kotlin.system.measureTimeMillis -import kotlin.time.measureTime class SubscriptionsFeedFragment : MainFragment() { override val isMainView : Boolean = true; @@ -321,7 +323,7 @@ class SubscriptionsFeedFragment : MainFragment() { withContext(Dispatchers.Main) { val results = cachePager.getResults(); 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); } } @@ -331,15 +333,14 @@ class SubscriptionsFeedFragment : MainFragment() { Logger.i(TAG, "Subscriptions load"); if(recyclerData.results.size == 0) { loadCache(); - } else { - setTextCentered(null); - } + } else + setEmptyPager(false); _taskGetPager.run(withRefetch); } override fun onRestoreCachedData(cachedData: RecyclerData, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>) { 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) { Logger.i(TAG, "Subscriptions new pager loaded (${pager.getResults().size})"); @@ -349,7 +350,7 @@ class SubscriptionsFeedFragment : MainFragment() { finishRefreshLayoutLoader(); setLoading(false); 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) { 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(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) { context?.let { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index edb2b381..5dade00d 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -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.IPlatformVideoDetails 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.structures.IPager 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.ScriptException 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.exceptions.UnsupportedCastException 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) } } + .exception { + Logger.w(TAG, "exception", 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 { Logger.w(TAG, "exception", it) diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt index 60552f23..3129637c 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -31,6 +31,7 @@ import com.futo.platformplayer.background.BackgroundWorker import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.constructs.Event0 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.SourceDetailFragment 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? { val locale = getLocaleSetting(baseContext); diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt index d499b226..5c8a3dbd 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt @@ -3,6 +3,7 @@ package com.futo.platformplayer.states import android.content.Context import com.futo.platformplayer.R import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.activities.LoginActivity import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.api.media.platforms.js.SourceAuth @@ -53,6 +54,25 @@ class StatePlugins { .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? { return try { val idField = c.getDeclaredField(resourceName) diff --git a/app/src/main/java/com/futo/platformplayer/views/NoResultsView.kt b/app/src/main/java/com/futo/platformplayer/views/NoResultsView.kt new file mode 100644 index 00000000..d3e710f5 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/NoResultsView.kt @@ -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) : 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); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt b/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt index 61f8013a..5db79617 100644 --- a/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt +++ b/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt @@ -76,6 +76,11 @@ open class BigButton : LinearLayout { _textSecondary.text = attrTextSecondary; } + fun withMargin(bottom: Int, side: Int = 0): BigButton { + setPadding(side, 0, side, bottom) + return this; + } + fun setSecondaryText(text: String?) { _textSecondary.text = text } diff --git a/app/src/main/res/drawable/background_big_primary.xml b/app/src/main/res/drawable/background_big_primary.xml new file mode 100644 index 00000000..2cd9f14d --- /dev/null +++ b/app/src/main/res/drawable/background_big_primary.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_explore.xml b/app/src/main/res/drawable/ic_explore.xml new file mode 100644 index 00000000..967f849f --- /dev/null +++ b/app/src/main/res/drawable/ic_explore.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_feed.xml b/app/src/main/res/layout/fragment_feed.xml index 6325134c..60cb7313 100644 --- a/app/src/main/res/layout/fragment_feed.xml +++ b/app/src/main/res/layout/fragment_feed.xml @@ -106,6 +106,11 @@ android:gravity="center" android:textColor="@color/gray_ac" android:textSize="12dp" /> + diff --git a/app/src/main/res/layout/fragment_import.xml b/app/src/main/res/layout/fragment_import.xml index 1df41949..1111853f 100644 --- a/app/src/main/res/layout/fragment_import.xml +++ b/app/src/main/res/layout/fragment_import.xml @@ -39,7 +39,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/unstable/assets/sources/nebula b/app/src/unstable/assets/sources/nebula index 1b08b18c..863d0be1 160000 --- a/app/src/unstable/assets/sources/nebula +++ b/app/src/unstable/assets/sources/nebula @@ -1 +1 @@ -Subproject commit 1b08b18c74f284cdd2b1858950efe6583fbcbef0 +Subproject commit 863d0be1322660c99e4d0cdae0b45d0a5918542d