Merge branch 'subscription-submission-modal' into 'master'

Subscription submission modal

See merge request videostreaming/grayjay!38
This commit is contained in:
Koen 2024-11-21 09:43:25 +00:00
commit f416f197bc
3 changed files with 216 additions and 131 deletions

View file

@ -6,6 +6,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.text.Layout
import android.text.method.ScrollingMovementMethod import android.text.method.ScrollingMovementMethod
import android.util.TypedValue import android.util.TypedValue
import android.view.Gravity import android.view.Gravity
@ -198,7 +199,6 @@ class UIDialogs {
dialog.show(); dialog.show();
} }
fun showDialog(context: Context, icon: Int, text: String, textDetails: String? = null, code: String? = null, defaultCloseAction: Int, vararg actions: Action) { fun showDialog(context: Context, icon: Int, text: String, textDetails: String? = null, code: String? = null, defaultCloseAction: Int, vararg actions: Action) {
val builder = AlertDialog.Builder(context); val builder = AlertDialog.Builder(context);
val view = LayoutInflater.from(context).inflate(R.layout.dialog_multi_button, null); val view = LayoutInflater.from(context).inflate(R.layout.dialog_multi_button, null);
@ -216,16 +216,18 @@ class UIDialogs {
view.findViewById<TextView>(R.id.dialog_text_details).apply { view.findViewById<TextView>(R.id.dialog_text_details).apply {
if (textDetails == null) if (textDetails == null)
this.visibility = View.GONE; this.visibility = View.GONE;
else else {
this.text = textDetails; this.text = textDetails;
this.textAlignment = View.TEXT_ALIGNMENT_VIEW_START
}
}; };
view.findViewById<TextView>(R.id.dialog_text_code).apply { view.findViewById<TextView>(R.id.dialog_text_code).apply {
if(code == null) if (code == null) this.visibility = View.GONE;
this.visibility = View.GONE;
else { else {
this.text = code; this.text = code;
this.movementMethod = ScrollingMovementMethod.getInstance(); this.movementMethod = ScrollingMovementMethod.getInstance();
this.visibility = View.VISIBLE; this.visibility = View.VISIBLE;
this.textAlignment = View.TEXT_ALIGNMENT_VIEW_START
} }
}; };
view.findViewById<LinearLayout>(R.id.dialog_buttons).apply { view.findViewById<LinearLayout>(R.id.dialog_buttons).apply {

View file

@ -7,7 +7,6 @@ import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.net.wifi.WifiManager
import android.os.Bundle import android.os.Bundle
import android.os.StrictMode import android.os.StrictMode
import android.os.StrictMode.VmPolicy import android.os.StrictMode.VmPolicy
@ -33,6 +32,7 @@ import com.futo.platformplayer.BuildConfig
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.Settings import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.casting.StateCasting
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment
@ -81,6 +81,7 @@ import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.states.StatePlaylists import com.futo.platformplayer.states.StatePlaylists
import com.futo.platformplayer.states.StateSubscriptions import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.platformplayer.stores.StringStorage
import com.futo.platformplayer.stores.SubscriptionStorage import com.futo.platformplayer.stores.SubscriptionStorage
import com.futo.platformplayer.stores.v2.ManagedStore import com.futo.platformplayer.stores.v2.ManagedStore
import com.futo.platformplayer.views.ToastView import com.futo.platformplayer.views.ToastView
@ -88,11 +89,14 @@ import com.futo.polycentric.core.ApiMethods
import com.google.gson.JsonParser import com.google.gson.JsonParser
import com.google.zxing.integration.android.IntentIntegrator import com.google.zxing.integration.android.IntentIntegrator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.File import java.io.File
import java.io.PrintWriter import java.io.PrintWriter
@ -102,7 +106,6 @@ import java.util.LinkedList
import java.util.Queue import java.util.Queue
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
class MainActivity : AppCompatActivity, IWithResultLauncher { class MainActivity : AppCompatActivity, IWithResultLauncher {
//TODO: Move to dimensions //TODO: Move to dimensions
@ -256,7 +259,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
setNavigationBarColorAndIcons(); setNavigationBarColorAndIcons();
if (Settings.instance.playback.allowVideoToGoUnderCutout) if (Settings.instance.playback.allowVideoToGoUnderCutout)
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES window.attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
runBlocking { runBlocking {
StatePlatform.instance.updateAvailableClients(this@MainActivity); StatePlatform.instance.updateAvailableClients(this@MainActivity);
@ -331,9 +335,11 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
}; };
_fragVideoDetail.onTransitioning.subscribe { _fragVideoDetail.onTransitioning.subscribe {
if (it || _fragVideoDetail.state != VideoDetailFragment.State.MINIMIZED) if (it || _fragVideoDetail.state != VideoDetailFragment.State.MINIMIZED)
_fragContainerOverlay.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15f, resources.displayMetrics); _fragContainerOverlay.elevation =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15f, resources.displayMetrics);
else else
_fragContainerOverlay.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics); _fragContainerOverlay.elevation =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics);
} }
_fragVideoDetail.onCloseEvent.subscribe { _fragVideoDetail.onCloseEvent.subscribe {
@ -353,8 +359,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
if (it) { if (it) {
_buttonIncognito.elevation = 99f; _buttonIncognito.elevation = 99f;
_buttonIncognito.alpha = 1f; _buttonIncognito.alpha = 1f;
} } else {
else {
_buttonIncognito.elevation = -99f; _buttonIncognito.elevation = -99f;
_buttonIncognito.alpha = 0f; _buttonIncognito.alpha = 0f;
} }
@ -362,14 +367,16 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
_buttonIncognito.setOnClickListener { _buttonIncognito.setOnClickListener {
if (!StateApp.instance.privateMode) if (!StateApp.instance.privateMode)
return@setOnClickListener; return@setOnClickListener;
UIDialogs.showDialog(this, R.drawable.ic_disabled_visible_purple, "Disable Privacy Mode", UIDialogs.showDialog(
this, R.drawable.ic_disabled_visible_purple, "Disable Privacy Mode",
"Do you want to disable privacy mode? New videos will be tracked again.", null, 0, "Do you want to disable privacy mode? New videos will be tracked again.", null, 0,
UIDialogs.Action("Cancel", { UIDialogs.Action("Cancel", {
StateApp.instance.setPrivacyMode(true); StateApp.instance.setPrivacyMode(true);
}, UIDialogs.ActionStyle.NONE), }, UIDialogs.ActionStyle.NONE),
UIDialogs.Action("Disable", { UIDialogs.Action("Disable", {
StateApp.instance.setPrivacyMode(false); StateApp.instance.setPrivacyMode(false);
}, UIDialogs.ActionStyle.DANGEROUS)); }, UIDialogs.ActionStyle.DANGEROUS)
);
}; };
_fragVideoDetail.onFullscreenChanged.subscribe { _fragVideoDetail.onFullscreenChanged.subscribe {
Logger.i(TAG, "onFullscreenChanged ${it}"); Logger.i(TAG, "onFullscreenChanged ${it}");
@ -377,13 +384,11 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
if (it) { if (it) {
_buttonIncognito.elevation = -99f; _buttonIncognito.elevation = -99f;
_buttonIncognito.alpha = 0f; _buttonIncognito.alpha = 0f;
} } else {
else {
if (StateApp.instance.privateMode) { if (StateApp.instance.privateMode) {
_buttonIncognito.elevation = 99f; _buttonIncognito.elevation = 99f;
_buttonIncognito.alpha = 1f; _buttonIncognito.alpha = 1f;
} } else {
else {
_buttonIncognito.elevation = -99f; _buttonIncognito.elevation = -99f;
_buttonIncognito.alpha = 0f; _buttonIncognito.alpha = 0f;
} }
@ -448,7 +453,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
fragCurrent = _fragMainHome; fragCurrent = _fragMainHome;
val defaultTab = Settings.instance.tabs.mapNotNull { val defaultTab = Settings.instance.tabs.mapNotNull {
val buttonDefinition = MenuBottomBarFragment.buttonDefinitions.firstOrNull { bd -> it.id == bd.id }; val buttonDefinition =
MenuBottomBarFragment.buttonDefinitions.firstOrNull { bd -> it.id == bd.id };
if (buttonDefinition == null) { if (buttonDefinition == null) {
return@mapNotNull null; return@mapNotNull null;
} else { } else {
@ -507,7 +513,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
//startActivity(Intent(this, TestActivity::class.java)); //startActivity(Intent(this, TestActivity::class.java));
val sharedPreferences = getSharedPreferences("GrayjayFirstBoot", Context.MODE_PRIVATE) val sharedPreferences =
getSharedPreferences("GrayjayFirstBoot", Context.MODE_PRIVATE)
val isFirstBoot = sharedPreferences.getBoolean("IsFirstBoot", true) val isFirstBoot = sharedPreferences.getBoolean("IsFirstBoot", true)
if (isFirstBoot) { if (isFirstBoot) {
UIDialogs.showConfirmationDialog(this, getString(R.string.do_you_want_to_see_the_tutorials_you_can_find_them_at_any_time_through_the_more_button), { UIDialogs.showConfirmationDialog(this, getString(R.string.do_you_want_to_see_the_tutorials_you_can_find_them_at_any_time_through_the_more_button), {
@ -516,6 +523,64 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
sharedPreferences.edit().putBoolean("IsFirstBoot", false).apply() sharedPreferences.edit().putBoolean("IsFirstBoot", false).apply()
} }
val submissionStatus = FragmentedStorage.get<StringStorage>("subscriptionSubmissionStatus")
val numSubscriptions = StateSubscriptions.instance.getSubscriptionCount()
val subscriptionsThreshold = 20
if (
submissionStatus.value == ""
&& StateApp.instance.getCurrentNetworkState() != StateApp.NetworkState.DISCONNECTED
&& numSubscriptions >= subscriptionsThreshold
) {
UIDialogs.showDialog(
this,
R.drawable.ic_internet,
getString(R.string.contribute_personal_subscriptions_list),
getString(R.string.contribute_personal_subscriptions_list_description),
null,
0,
UIDialogs.Action("Cancel", {
submissionStatus.setAndSave("dismissed")
}, UIDialogs.ActionStyle.NONE),
UIDialogs.Action("Upload", {
submissionStatus.setAndSave("submitted")
GlobalScope.launch(Dispatchers.IO) {
@Serializable
data class CreatorInfo(val pluginId: String, val url: String)
val subscriptions =
StateSubscriptions.instance.getSubscriptions().map { original ->
CreatorInfo(
pluginId = original.channel.id.pluginId ?: "",
url = original.channel.url
)
}
val json = Json.encodeToString(subscriptions)
val url = "https://data.grayjay.app/donate-subscription-list"
val client = ManagedHttpClient();
val headers = hashMapOf(
"Content-Type" to "application/json"
)
try {
val response = client.post(url, json, headers)
// if it failed retry one time
if (!response.isOk) {
client.post(url, json, headers)
}
} catch (e: Exception) {
Logger.i(TAG, "Failed to submit subscription list.", e)
}
}
}, UIDialogs.ActionStyle.PRIMARY)
)
}
} }
/* /*
@ -589,9 +654,11 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
when (intent.action) { when (intent.action) {
Intent.ACTION_SEND -> { Intent.ACTION_SEND -> {
targetData = intent.getStringExtra(Intent.EXTRA_STREAM) ?: intent.getStringExtra(Intent.EXTRA_TEXT); targetData = intent.getStringExtra(Intent.EXTRA_STREAM)
?: intent.getStringExtra(Intent.EXTRA_TEXT);
Logger.i(TAG, "Share Received: " + targetData); Logger.i(TAG, "Share Received: " + targetData);
} }
Intent.ACTION_VIEW -> { Intent.ACTION_VIEW -> {
targetData = intent.dataString targetData = intent.dataString
@ -599,18 +666,22 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
Logger.i(TAG, "View Received: " + targetData); Logger.i(TAG, "View Received: " + targetData);
} }
} }
"VIDEO" -> { "VIDEO" -> {
val url = intent.getStringExtra("VIDEO"); val url = intent.getStringExtra("VIDEO");
navigate(_fragVideoDetail, url); navigate(_fragVideoDetail, url);
} }
"IMPORT_OPTIONS" -> { "IMPORT_OPTIONS" -> {
UIDialogs.showImportOptionsDialog(this); UIDialogs.showImportOptionsDialog(this);
} }
"ACTION" -> { "ACTION" -> {
val action = intent.getStringExtra("ACTION"); val action = intent.getStringExtra("ACTION");
StateDeveloper.instance.testState = "TestPlayback"; StateDeveloper.instance.testState = "TestPlayback";
StateDeveloper.instance.testPlayback(); StateDeveloper.instance.testPlayback();
} }
"TAB" -> { "TAB" -> {
when (intent.getStringExtra("TAB")) { when (intent.getStringExtra("TAB")) {
"Sources" -> { "Sources" -> {
@ -642,8 +713,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
handleUrlAll(targetData) handleUrlAll(targetData)
} }
} }
} } catch (ex: Throwable) {
catch(ex: Throwable) {
UIDialogs.showGeneralErrorDialog(this, getString(R.string.failed_to_handle_file), ex); UIDialogs.showGeneralErrorDialog(this, getString(R.string.failed_to_handle_file), ex);
} }
} }
@ -653,32 +723,28 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
when (uri.scheme) { when (uri.scheme) {
"grayjay" -> { "grayjay" -> {
if (url.startsWith("grayjay://license/")) { if (url.startsWith("grayjay://license/")) {
if(StatePayment.instance.setPaymentLicenseUrl(url)) if (StatePayment.instance.setPaymentLicenseUrl(url)) {
{
UIDialogs.showDialogOk(this, R.drawable.ic_check, getString(R.string.your_license_key_has_been_set_an_app_restart_might_be_required)); UIDialogs.showDialogOk(this, R.drawable.ic_check, getString(R.string.your_license_key_has_been_set_an_app_restart_might_be_required));
if (fragCurrent is BuyFragment) if (fragCurrent is BuyFragment)
closeSegment(fragCurrent); closeSegment(fragCurrent);
} } else
else
UIDialogs.toast(getString(R.string.invalid_license_format)); UIDialogs.toast(getString(R.string.invalid_license_format));
} } else if (url.startsWith("grayjay://plugin/")) {
else if(url.startsWith("grayjay://plugin/")) {
val intent = Intent(this, AddSourceActivity::class.java).apply { val intent = Intent(this, AddSourceActivity::class.java).apply {
data = Uri.parse(url.substring("grayjay://plugin/".length)); data = Uri.parse(url.substring("grayjay://plugin/".length));
}; };
startActivity(intent); startActivity(intent);
} } else if (url.startsWith("grayjay://video/")) {
else if(url.startsWith("grayjay://video/")) {
val videoUrl = url.substring("grayjay://video/".length); val videoUrl = url.substring("grayjay://video/".length);
navigate(_fragVideoDetail, videoUrl); navigate(_fragVideoDetail, videoUrl);
} } else if (url.startsWith("grayjay://channel/")) {
else if(url.startsWith("grayjay://channel/")) {
val channelUrl = url.substring("grayjay://channel/".length); val channelUrl = url.substring("grayjay://channel/".length);
navigate(_fragMainChannel, channelUrl); navigate(_fragMainChannel, channelUrl);
} }
} }
"content" -> { "content" -> {
if (!handleContent(url, intent.type)) { if (!handleContent(url, intent.type)) {
UIDialogs.showSingleButtonDialog( UIDialogs.showSingleButtonDialog(
@ -689,6 +755,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
{ }); { });
} }
} }
"file" -> { "file" -> {
if (!handleFile(url)) { if (!handleFile(url)) {
UIDialogs.showSingleButtonDialog( UIDialogs.showSingleButtonDialog(
@ -699,6 +766,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
{ }); { });
} }
} }
"polycentric" -> { "polycentric" -> {
if (!handlePolycentric(url)) { if (!handlePolycentric(url)) {
UIDialogs.showSingleButtonDialog( UIDialogs.showSingleButtonDialog(
@ -709,6 +777,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
{ }); { });
} }
} }
"fcast" -> { "fcast" -> {
if (!handleFCast(url)) { if (!handleFCast(url)) {
UIDialogs.showSingleButtonDialog( UIDialogs.showSingleButtonDialog(
@ -719,6 +788,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
{ }); { });
} }
} }
else -> { else -> {
if (!handleUrl(url)) { if (!handleUrl(url)) {
UIDialogs.showSingleButtonDialog( UIDialogs.showSingleButtonDialog(
@ -768,6 +838,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
return@withContext false; return@withContext false;
} }
} }
fun handleContent(file: String, mime: String? = null): Boolean { fun handleContent(file: String, mime: String? = null): Boolean {
Logger.i(TAG, "handleContent(url=$file)"); Logger.i(TAG, "handleContent(url=$file)");
@ -778,14 +849,14 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
return handleUnknownJson(recon); return handleUnknownJson(recon);
var reconLines = Json.decodeFromString<List<String>>(recon); var reconLines = Json.decodeFromString<List<String>>(recon);
val cacheStr = reconLines.find { it.startsWith("__CACHE:") }?.substring("__CACHE:".length); val cacheStr =
reconLines.find { it.startsWith("__CACHE:") }?.substring("__CACHE:".length);
reconLines = reconLines.filter { !it.startsWith("__CACHE:") }; //TODO: constant prefix reconLines = reconLines.filter { !it.startsWith("__CACHE:") }; //TODO: constant prefix
var cache: ImportCache? = null; var cache: ImportCache? = null;
try { try {
if (cacheStr != null) if (cacheStr != null)
cache = Json.decodeFromString(cacheStr); cache = Json.decodeFromString(cacheStr);
} } catch (ex: Throwable) {
catch(ex: Throwable) {
Logger.e(TAG, "Failed to deserialize cache"); Logger.e(TAG, "Failed to deserialize cache");
} }
@ -794,16 +865,15 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
Logger.i(TAG, "Opened shared playlist reconstruction\n${recon}"); Logger.i(TAG, "Opened shared playlist reconstruction\n${recon}");
handleReconstruction(recon, cache); handleReconstruction(recon, cache);
return true; return true;
} } else if (file.lowercase().endsWith(".zip") || mime == "application/zip") {
else if(file.lowercase().endsWith(".zip") || mime == "application/zip") {
StateBackup.importZipBytes(this, lifecycleScope, data); StateBackup.importZipBytes(this, lifecycleScope, data);
return true; return true;
} } else if (file.lowercase().endsWith(".txt") || mime == "text/plain") {
else if(file.lowercase().endsWith(".txt") || mime == "text/plain") {
return handleUnknownText(String(data)); return handleUnknownText(String(data));
} }
return false; return false;
} }
fun handleFile(file: String): Boolean { fun handleFile(file: String): Boolean {
Logger.i(TAG, "handleFile(url=$file)"); Logger.i(TAG, "handleFile(url=$file)");
if (file.lowercase().endsWith(".json")) { if (file.lowercase().endsWith(".json")) {
@ -812,14 +882,14 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
return handleUnknownJson(recon); return handleUnknownJson(recon);
var reconLines = Json.decodeFromString<List<String>>(recon); var reconLines = Json.decodeFromString<List<String>>(recon);
val cacheStr = reconLines.find { it.startsWith("__CACHE:") }?.substring("__CACHE:".length); val cacheStr =
reconLines.find { it.startsWith("__CACHE:") }?.substring("__CACHE:".length);
reconLines = reconLines.filter { !it.startsWith("__CACHE:") }; //TODO: constant prefix reconLines = reconLines.filter { !it.startsWith("__CACHE:") }; //TODO: constant prefix
var cache: ImportCache? = null; var cache: ImportCache? = null;
try { try {
if (cacheStr != null) if (cacheStr != null)
cache = Json.decodeFromString(cacheStr); cache = Json.decodeFromString(cacheStr);
} } catch (ex: Throwable) {
catch(ex: Throwable) {
Logger.e(TAG, "Failed to deserialize cache"); Logger.e(TAG, "Failed to deserialize cache");
} }
recon = reconLines.joinToString("\n"); recon = reconLines.joinToString("\n");
@ -827,16 +897,15 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
Logger.i(TAG, "Opened shared playlist reconstruction\n${recon}"); Logger.i(TAG, "Opened shared playlist reconstruction\n${recon}");
handleReconstruction(recon, cache); handleReconstruction(recon, cache);
return true; return true;
} } else if (file.lowercase().endsWith(".zip")) {
else if(file.lowercase().endsWith(".zip")) {
StateBackup.importZipBytes(this, lifecycleScope, readSharedFile(file)); StateBackup.importZipBytes(this, lifecycleScope, readSharedFile(file));
return true; return true;
} } else if (file.lowercase().endsWith(".txt")) {
else if(file.lowercase().endsWith(".txt")) {
return handleUnknownText(String(readSharedFile(file))); return handleUnknownText(String(readSharedFile(file)));
} }
return false; return false;
} }
fun handleReconstruction(recon: String, cache: ImportCache? = null) { fun handleReconstruction(recon: String, cache: ImportCache? = null) {
val type = ManagedStore.getReconstructionIdentifier(recon); val type = ManagedStore.getReconstructionIdentifier(recon);
val store: ManagedStore<*> = when (type) { val store: ManagedStore<*> = when (type) {
@ -848,7 +917,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
}; };
val name = when (type) { val name = when (type) {
"Playlist" -> recon.split("\n").filter { !it.startsWith(ManagedStore.RECONSTRUCTION_HEADER_OPERATOR) }.firstOrNull() ?: type; "Playlist" -> recon.split("\n")
.filter { !it.startsWith(ManagedStore.RECONSTRUCTION_HEADER_OPERATOR) }
.firstOrNull() ?: type;
else -> type else -> type
} }
@ -867,13 +938,13 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
navigate(_fragImportSubscriptions, lines); navigate(_fragImportSubscriptions, lines);
return true; return true;
} }
} } catch (ex: Throwable) {
catch(ex: Throwable) {
Logger.e(TAG, ex.message, ex); Logger.e(TAG, ex.message, ex);
UIDialogs.showGeneralErrorDialog(this, getString(R.string.failed_to_parse_text_file), ex); UIDialogs.showGeneralErrorDialog(this, getString(R.string.failed_to_parse_text_file), ex);
} }
return false; return false;
} }
fun handleUnknownJson(json: String): Boolean { fun handleUnknownJson(json: String): Boolean {
val context = this; val context = this;
@ -885,8 +956,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
return false;//throw IllegalArgumentException("Invalid NewPipe json structure found"); return false;//throw IllegalArgumentException("Invalid NewPipe json structure found");
StateBackup.importNewPipeSubs(this, newPipeSubsParsed); StateBackup.importNewPipeSubs(this, newPipeSubsParsed);
} } catch (ex: Exception) {
catch(ex: Exception) {
Logger.e(TAG, ex.message, ex); Logger.e(TAG, ex.message, ex);
UIDialogs.showGeneralErrorDialog(context, getString(R.string.failed_to_parse_newpipe_subscriptions), ex); UIDialogs.showGeneralErrorDialog(context, getString(R.string.failed_to_parse_newpipe_subscriptions), ex);
} }
@ -1005,7 +1075,6 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
return; return;
} }
fragCurrent.onHide(); fragCurrent.onHide();
if (segment.isMainView) { if (segment.isMainView) {
@ -1017,8 +1086,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
.replace(R.id.fragment_top_bar, segment.topBar as Fragment); .replace(R.id.fragment_top_bar, segment.topBar as Fragment);
fragCurrent.topBar?.onHide(); fragCurrent.topBar?.onHide();
} }
} } else if (fragCurrent.topBar != null)
else if(fragCurrent.topBar != null)
transaction.hide(fragCurrent.topBar as Fragment); transaction.hide(fragCurrent.topBar as Fragment);
transaction = transaction.replace(R.id.fragment_main, segment); transaction = transaction.replace(R.id.fragment_main, segment);
@ -1026,8 +1094,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
if (segment.hasBottomBar) { if (segment.hasBottomBar) {
if (!fragCurrent.hasBottomBar) if (!fragCurrent.hasBottomBar)
transaction = transaction.show(_fragBotBarMenu); transaction = transaction.show(_fragBotBarMenu);
} } else {
else {
if (fragCurrent.hasBottomBar) if (fragCurrent.hasBottomBar)
transaction = transaction.hide(_fragBotBarMenu); transaction = transaction.hide(_fragBotBarMenu);
} }
@ -1130,12 +1197,18 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
if (fragCurrent.hasBottomBar) if (fragCurrent.hasBottomBar)
paddingBottom += HEIGHT_MENU_DP; paddingBottom += HEIGHT_MENU_DP;
_fragContainerOverlay.setPadding(0,0,0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom - HEIGHT_MENU_DP, resources.displayMetrics).toInt()); _fragContainerOverlay.setPadding(
0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom - HEIGHT_MENU_DP, resources.displayMetrics)
.toInt()
);
if (_fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED) if (_fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED)
paddingBottom += HEIGHT_VIDEO_MINIMIZED_DP; paddingBottom += HEIGHT_VIDEO_MINIMIZED_DP;
_fragContainerMain.setPadding(0,0,0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, resources.displayMetrics).toInt()); _fragContainerMain.setPadding(
0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, resources.displayMetrics)
.toInt()
);
} }
@ -1151,14 +1224,18 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
ContextCompat.checkSelfPermission(this, notifPermission) == PackageManager.PERMISSION_GRANTED -> { ContextCompat.checkSelfPermission(this, notifPermission) == PackageManager.PERMISSION_GRANTED -> {
} }
ActivityCompat.shouldShowRequestPermissionRationale(this, notifPermission) -> { ActivityCompat.shouldShowRequestPermissionRationale(this, notifPermission) -> {
UIDialogs.showDialog(this, R.drawable.ic_notifications, "Notifications Required", UIDialogs.showDialog(
this, R.drawable.ic_notifications, "Notifications Required",
reason, null, 0, reason, null, 0,
UIDialogs.Action("Cancel", {}), UIDialogs.Action("Cancel", {}),
UIDialogs.Action("Enable", { UIDialogs.Action("Enable", {
requestPermissionLauncher.launch(notifPermission); requestPermissionLauncher.launch(notifPermission);
}, UIDialogs.ActionStyle.PRIMARY)); }, UIDialogs.ActionStyle.PRIMARY)
);
} }
else -> { else -> {
requestPermissionLauncher.launch(notifPermission); requestPermissionLauncher.launch(notifPermission);
} }
@ -1176,6 +1253,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
}; };
} }
} }
private suspend fun launchAppToastJob() { private suspend fun launchAppToastJob() {
Logger.i(TAG, "Starting appToast loop"); Logger.i(TAG, "Starting appToast loop");
while (!_toastQueue.isEmpty()) { while (!_toastQueue.isEmpty()) {
@ -1208,14 +1286,15 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
private var resultLauncherMap = mutableMapOf<Int, (ActivityResult) -> Unit>(); private var resultLauncherMap = mutableMapOf<Int, (ActivityResult) -> Unit>();
private var requestCode: Int? = -1; private var requestCode: Int? = -1;
private val resultLauncher: ActivityResultLauncher<Intent> = registerForActivityResult( private val resultLauncher: ActivityResultLauncher<Intent> = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()) { ActivityResultContracts.StartActivityForResult()
result: ActivityResult -> ) { result: ActivityResult ->
val handler = synchronized(resultLauncherMap) { val handler = synchronized(resultLauncherMap) {
resultLauncherMap.remove(requestCode); resultLauncherMap.remove(requestCode);
} }
if (handler != null) if (handler != null)
handler(result); handler(result);
}; };
override fun launchForResult(intent: Intent, code: Int, handler: (ActivityResult) -> Unit) { override fun launchForResult(intent: Intent, code: Int, handler: (ActivityResult) -> Unit) {
synchronized(resultLauncherMap) { synchronized(resultLauncherMap) {
resultLauncherMap[code] = handler; resultLauncherMap[code] = handler;
@ -1234,6 +1313,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
return sourcesIntent; return sourcesIntent;
} }
fun getVideoIntent(context: Context, videoUrl: String): Intent { fun getVideoIntent(context: Context, videoUrl: String): Intent {
val sourcesIntent = Intent(context, MainActivity::class.java); val sourcesIntent = Intent(context, MainActivity::class.java);
sourcesIntent.action = "VIDEO"; sourcesIntent.action = "VIDEO";
@ -1241,6 +1321,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
return sourcesIntent; return sourcesIntent;
} }
fun getActionIntent(context: Context, action: String): Intent { fun getActionIntent(context: Context, action: String): Intent {
val sourcesIntent = Intent(context, MainActivity::class.java); val sourcesIntent = Intent(context, MainActivity::class.java);
sourcesIntent.action = "ACTION"; sourcesIntent.action = "ACTION";

View file

@ -810,6 +810,8 @@
<string name="scroll_to_top">Scroll to top</string> <string name="scroll_to_top">Scroll to top</string>
<string name="disable_battery_optimization">Disable Battery Optimization</string> <string name="disable_battery_optimization">Disable Battery Optimization</string>
<string name="click_to_go_to_battery_optimization_settings_disabling_battery_optimization_will_prevent_the_os_from_killing_media_sessions">Click to go to battery optimization settings. Disabling battery optimization will prevent the OS from killing media sessions.</string> <string name="click_to_go_to_battery_optimization_settings_disabling_battery_optimization_will_prevent_the_os_from_killing_media_sessions">Click to go to battery optimization settings. Disabling battery optimization will prevent the OS from killing media sessions.</string>
<string name="contribute_personal_subscriptions_list">Contribute Personal Subscriptions List</string>
<string name="contribute_personal_subscriptions_list_description">\nWould you liked to contribute your current creator subscriptions list to FUTO?\n\nThe data will be handled according to the Grayjay privacy policy. That is the list will be anonymized and stored without any reference to whomever the list of creators belonged to.\n\nThe intention is for Grayjay and FUTO to use these data to build a cross platform creator recommendation system to make it easier to find new creators you might like from within Grayjay.</string>
<string name="cd_cast_button">Cast button</string> <string name="cd_cast_button">Cast button</string>
<string name="cd_incognito_button">Incognito button</string> <string name="cd_incognito_button">Incognito button</string>
<string name="cd_creator_thumbnail">Creator thumbnail</string> <string name="cd_creator_thumbnail">Creator thumbnail</string>