fix desktop app and android app
This commit is contained in:
@@ -10,7 +10,8 @@ data class Instance(
|
|||||||
var apiKey: String,
|
var apiKey: String,
|
||||||
var defaultRating: String = "none",
|
var defaultRating: String = "none",
|
||||||
var defaultTags: String = "",
|
var defaultTags: String = "",
|
||||||
var defaultVisibility: String = "0"
|
var defaultVisibility: String = "0",
|
||||||
|
var resultLinkType: String = "post"
|
||||||
) {
|
) {
|
||||||
fun toJsonObject(): JSONObject {
|
fun toJsonObject(): JSONObject {
|
||||||
return JSONObject().apply {
|
return JSONObject().apply {
|
||||||
@@ -21,6 +22,7 @@ data class Instance(
|
|||||||
put("defaultRating", defaultRating)
|
put("defaultRating", defaultRating)
|
||||||
put("defaultTags", defaultTags)
|
put("defaultTags", defaultTags)
|
||||||
put("defaultVisibility", defaultVisibility)
|
put("defaultVisibility", defaultVisibility)
|
||||||
|
put("resultLinkType", resultLinkType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +37,8 @@ data class Instance(
|
|||||||
apiKey = json.optString("apiKey", ""),
|
apiKey = json.optString("apiKey", ""),
|
||||||
defaultRating = json.optString("defaultRating", "none"),
|
defaultRating = json.optString("defaultRating", "none"),
|
||||||
defaultTags = json.optString("defaultTags", ""),
|
defaultTags = json.optString("defaultTags", ""),
|
||||||
defaultVisibility = json.optString("defaultVisibility", "0")
|
defaultVisibility = json.optString("defaultVisibility", "0"),
|
||||||
|
resultLinkType = json.optString("resultLinkType", "post")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
setupRatingSpinner()
|
setupRatingSpinner()
|
||||||
setupVisibilitySpinner()
|
setupVisibilitySpinner()
|
||||||
|
setupLinkTypeSpinner()
|
||||||
setupInstanceSpinner()
|
setupInstanceSpinner()
|
||||||
|
|
||||||
binding.btnSave.setOnClickListener {
|
binding.btnSave.setOnClickListener {
|
||||||
@@ -63,6 +64,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
binding.spinnerVisibility.adapter = adapter
|
binding.spinnerVisibility.adapter = adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupLinkTypeSpinner() {
|
||||||
|
val types = arrayOf("Post Link (Viewer)", "Direct File Link")
|
||||||
|
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, types)
|
||||||
|
binding.spinnerLinkType.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupInstanceSpinner() {
|
private fun setupInstanceSpinner() {
|
||||||
currentInstances = prefs.getInstances()
|
currentInstances = prefs.getInstances()
|
||||||
if (currentInstances.isEmpty()) {
|
if (currentInstances.isEmpty()) {
|
||||||
@@ -106,6 +113,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
"2" -> binding.spinnerVisibility.setSelection(2)
|
"2" -> binding.spinnerVisibility.setSelection(2)
|
||||||
else -> binding.spinnerVisibility.setSelection(0)
|
else -> binding.spinnerVisibility.setSelection(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (instance.resultLinkType == "direct") {
|
||||||
|
binding.spinnerLinkType.setSelection(1)
|
||||||
|
} else {
|
||||||
|
binding.spinnerLinkType.setSelection(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveSettings() {
|
private fun saveSettings() {
|
||||||
@@ -125,6 +138,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
currentInstance.defaultVisibility = binding.spinnerVisibility.selectedItemPosition.toString()
|
currentInstance.defaultVisibility = binding.spinnerVisibility.selectedItemPosition.toString()
|
||||||
|
|
||||||
|
currentInstance.resultLinkType = if (binding.spinnerLinkType.selectedItemPosition == 1) "direct" else "post"
|
||||||
|
|
||||||
prefs.updateInstance(currentInstance)
|
prefs.updateInstance(currentInstance)
|
||||||
|
|
||||||
// Refresh spinner to show updated name
|
// Refresh spinner to show updated name
|
||||||
|
|||||||
@@ -6,11 +6,16 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.fuggloader.app.databinding.ActivityShareBinding
|
import com.fuggloader.app.databinding.ActivityShareBinding
|
||||||
|
import com.fuggloader.app.databinding.ItemUploadResultBinding
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -21,7 +26,10 @@ class ShareActivity : AppCompatActivity() {
|
|||||||
private lateinit var prefs: PreferencesManager
|
private lateinit var prefs: PreferencesManager
|
||||||
private lateinit var uploader: UploaderService
|
private lateinit var uploader: UploaderService
|
||||||
|
|
||||||
private var uploadedUrl: String? = null
|
private val uploadedResults = mutableListOf<UploadResult>()
|
||||||
|
private lateinit var resultAdapter: UploadResultAdapter
|
||||||
|
|
||||||
|
data class UploadResult(val name: String, val url: String)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -31,6 +39,8 @@ class ShareActivity : AppCompatActivity() {
|
|||||||
prefs = PreferencesManager(this)
|
prefs = PreferencesManager(this)
|
||||||
uploader = UploaderService(prefs)
|
uploader = UploaderService(prefs)
|
||||||
|
|
||||||
|
setupRecyclerView()
|
||||||
|
|
||||||
if (!prefs.isConfigured()) {
|
if (!prefs.isConfigured()) {
|
||||||
Toast.makeText(this, "fuggloader is not configured. Please open app settings first.", Toast.LENGTH_LONG).show()
|
Toast.makeText(this, "fuggloader is not configured. Please open app settings first.", Toast.LENGTH_LONG).show()
|
||||||
val intent = Intent(this, MainActivity::class.java)
|
val intent = Intent(this, MainActivity::class.java)
|
||||||
@@ -40,17 +50,10 @@ class ShareActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.btnCopy.setOnClickListener {
|
binding.btnCopy.setOnClickListener {
|
||||||
uploadedUrl?.let { url ->
|
if (uploadedResults.isNotEmpty()) {
|
||||||
copyToClipboard(url)
|
val text = uploadedResults.joinToString("\n") { it.url }
|
||||||
Toast.makeText(this, "URL copied to clipboard!", Toast.LENGTH_SHORT).show()
|
copyToClipboard(text)
|
||||||
}
|
Toast.makeText(this, "All URLs copied to clipboard!", Toast.LENGTH_SHORT).show()
|
||||||
}
|
|
||||||
|
|
||||||
binding.btnOpen.setOnClickListener {
|
|
||||||
uploadedUrl?.let { url ->
|
|
||||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
|
||||||
startActivity(browserIntent)
|
|
||||||
finish()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,65 +64,214 @@ class ShareActivity : AppCompatActivity() {
|
|||||||
prepareShare(intent)
|
prepareShare(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareShare(intent: Intent) {
|
private fun setupRecyclerView() {
|
||||||
val instances = prefs.getInstances()
|
resultAdapter = UploadResultAdapter(uploadedResults) { result, action ->
|
||||||
if (instances.size > 1) {
|
when (action) {
|
||||||
val names = instances.map { it.name }.toTypedArray()
|
ResultAction.COPY -> {
|
||||||
AlertDialog.Builder(this)
|
copyToClipboard(result.url)
|
||||||
.setTitle("Select Instance")
|
Toast.makeText(this, "URL copied!", Toast.LENGTH_SHORT).show()
|
||||||
.setItems(names) { _, which ->
|
}
|
||||||
handleShareIntent(instances[which], intent)
|
ResultAction.OPEN -> {
|
||||||
|
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(result.url))
|
||||||
|
startActivity(browserIntent)
|
||||||
}
|
}
|
||||||
.setNegativeButton("Cancel") { _, _ -> finish() }
|
|
||||||
.setCancelable(false)
|
|
||||||
.show()
|
|
||||||
} else {
|
|
||||||
val target = instances.firstOrNull()
|
|
||||||
if (target != null) {
|
|
||||||
handleShareIntent(target, intent)
|
|
||||||
} else {
|
|
||||||
showError("No instance configured")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding.rvResults.layoutManager = LinearLayoutManager(this)
|
||||||
|
binding.rvResults.adapter = resultAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ResultAction { COPY, OPEN }
|
||||||
|
|
||||||
|
inner class UploadResultAdapter(
|
||||||
|
private val items: List<UploadResult>,
|
||||||
|
private val onAction: (UploadResult, ResultAction) -> Unit
|
||||||
|
) : RecyclerView.Adapter<UploadResultAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
inner class ViewHolder(val itemBinding: ItemUploadResultBinding) : RecyclerView.ViewHolder(itemBinding.root)
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val b = ItemUploadResultBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return ViewHolder(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val item = items[position]
|
||||||
|
holder.itemBinding.tvFileName.text = item.name
|
||||||
|
holder.itemBinding.tvFileUrl.text = item.url
|
||||||
|
holder.itemBinding.btnItemCopy.setOnClickListener { onAction(item, ResultAction.COPY) }
|
||||||
|
holder.itemBinding.btnItemOpen.setOnClickListener { onAction(item, ResultAction.OPEN) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareShare(intent: Intent) {
|
||||||
|
val target = prefs.getActiveInstance()
|
||||||
|
if (target != null) {
|
||||||
|
handleShareIntent(target, intent)
|
||||||
|
} else {
|
||||||
|
showError("No instance configured")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleShareIntent(instance: Instance, intent: Intent) {
|
private fun handleShareIntent(instance: Instance, intent: Intent) {
|
||||||
val action = intent.action
|
val action = intent.action
|
||||||
val type = intent.type
|
val type = intent.type
|
||||||
|
|
||||||
|
val uris = mutableListOf<Uri>()
|
||||||
|
var sharedUrl: String? = null
|
||||||
|
|
||||||
if (Intent.ACTION_SEND == action && type != null) {
|
if (Intent.ACTION_SEND == action && type != null) {
|
||||||
if ("text/plain" == type) {
|
if ("text/plain" == type) {
|
||||||
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
|
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
if (!sharedText.isNull_or_blank_url()) {
|
if (!sharedText.isNull_or_blank_url()) {
|
||||||
val targetUrl = extractUrl(sharedText!!)
|
sharedUrl = extractUrl(sharedText!!)
|
||||||
processUrlUpload(instance, targetUrl)
|
|
||||||
} else {
|
} else {
|
||||||
val streamUri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
val streamUri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
||||||
if (streamUri != null) {
|
if (streamUri != null) {
|
||||||
processFileUpload(instance, streamUri)
|
uris.add(streamUri)
|
||||||
} else {
|
|
||||||
showError("No valid file or URL found in share intent")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val streamUri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
val streamUri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
||||||
if (streamUri != null) {
|
if (streamUri != null) {
|
||||||
processFileUpload(instance, streamUri)
|
uris.add(streamUri)
|
||||||
} else {
|
|
||||||
showError("No stream URI found for file share")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (Intent.ACTION_SEND_MULTIPLE == action) {
|
} else if (Intent.ACTION_SEND_MULTIPLE == action) {
|
||||||
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
|
val streamUris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
|
||||||
if (!uris.isNullOrEmpty()) {
|
if (streamUris != null) {
|
||||||
// Upload first file in batch for now
|
uris.addAll(streamUris)
|
||||||
processFileUpload(instance, uris[0])
|
|
||||||
} else {
|
|
||||||
showError("No files found in multi-share intent")
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
showError("Unsupported share action: $action")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sharedUrl != null) {
|
||||||
|
processUrlUpload(instance, sharedUrl)
|
||||||
|
} else if (uris.isNotEmpty()) {
|
||||||
|
processBatchUpload(instance, uris)
|
||||||
|
} else {
|
||||||
|
showError("No valid file or URL found in share intent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processBatchUpload(instance: Instance, uris: List<Uri>) {
|
||||||
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
binding.progressBar.isIndeterminate = false
|
||||||
|
binding.progressBar.progress = 0
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
for ((index, uri) in uris.withIndex()) {
|
||||||
|
val displayIndex = index + 1
|
||||||
|
val fileName = getFileName(uri) ?: "File $displayIndex"
|
||||||
|
binding.tvStatus.text = "Uploading $displayIndex of ${uris.size}: $fileName"
|
||||||
|
binding.tvPercent.text = "0%"
|
||||||
|
binding.progressBar.progress = 0
|
||||||
|
|
||||||
|
val result = uploader.uploadFile(
|
||||||
|
contentResolver = contentResolver,
|
||||||
|
uri = uri,
|
||||||
|
instance = instance,
|
||||||
|
onProgress = { pct ->
|
||||||
|
runOnUiThread {
|
||||||
|
binding.progressBar.progress = pct
|
||||||
|
binding.tvPercent.text = "$pct%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
result.fold(
|
||||||
|
onSuccess = { url ->
|
||||||
|
uploadedResults.add(UploadResult(fileName, url))
|
||||||
|
runOnUiThread {
|
||||||
|
resultAdapter.notifyItemInserted(uploadedResults.size - 1)
|
||||||
|
binding.rvResults.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
if (index == uris.size - 1) {
|
||||||
|
showSuccess()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = { err ->
|
||||||
|
showError("Upload $displayIndex failed: ${err.message}")
|
||||||
|
return@launch // Stop on first error
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processUrlUpload(instance: Instance, url: String) {
|
||||||
|
binding.tvStatus.text = "Uploading URL to ${instance.name}..."
|
||||||
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
binding.progressBar.isIndeterminate = true
|
||||||
|
binding.tvPercent.text = ""
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
val result = uploader.uploadUrl(url, instance)
|
||||||
|
result.fold(
|
||||||
|
onSuccess = { postUrl ->
|
||||||
|
uploadedResults.add(UploadResult("Shared URL", postUrl))
|
||||||
|
runOnUiThread {
|
||||||
|
resultAdapter.notifyItemInserted(uploadedResults.size - 1)
|
||||||
|
binding.rvResults.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
showSuccess()
|
||||||
|
},
|
||||||
|
onFailure = { err -> showError(err.message ?: "URL Upload failed") }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showSuccess() {
|
||||||
|
if (uploadedResults.isEmpty()) return
|
||||||
|
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
binding.tvPercent.text = "100%"
|
||||||
|
|
||||||
|
if (uploadedResults.size > 1) {
|
||||||
|
binding.tvStatus.text = "All ${uploadedResults.size} uploads complete!"
|
||||||
|
binding.btnCopy.text = "Copy All Links"
|
||||||
|
|
||||||
|
val allLinks = uploadedResults.joinToString("\n") { it.url }
|
||||||
|
copyToClipboard(allLinks)
|
||||||
|
} else {
|
||||||
|
binding.tvStatus.text = "Upload complete!"
|
||||||
|
binding.btnCopy.text = "Copy Link"
|
||||||
|
copyToClipboard(uploadedResults.first().url)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.layoutButtons.visibility = View.VISIBLE
|
||||||
|
Toast.makeText(this, "Uploaded! Links copied to clipboard.", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFileName(uri: Uri): String? {
|
||||||
|
var result: String? = null
|
||||||
|
if (uri.scheme == "content") {
|
||||||
|
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
val index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||||
|
if (index != -1) result = cursor.getString(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = uri.path
|
||||||
|
val cut = result?.lastIndexOf('/') ?: -1
|
||||||
|
if (cut != -1) result = result?.substring(cut + 1)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showError(msg: String) {
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
binding.tvStatus.text = "Error: $msg"
|
||||||
|
binding.btnClose.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyToClipboard(text: String) {
|
||||||
|
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clip = ClipData.newPlainText("fuggloader link", text)
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String?.isNull_or_blank_url(): Boolean {
|
private fun String?.isNull_or_blank_url(): Boolean {
|
||||||
@@ -138,71 +290,4 @@ class ShareActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
return trimmed
|
return trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processFileUpload(instance: Instance, uri: Uri) {
|
|
||||||
binding.tvStatus.text = "Uploading to ${instance.name}..."
|
|
||||||
binding.progressBar.visibility = View.VISIBLE
|
|
||||||
binding.progressBar.isIndeterminate = false
|
|
||||||
binding.progressBar.progress = 0
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
val result = uploader.uploadFile(
|
|
||||||
contentResolver = contentResolver,
|
|
||||||
uri = uri,
|
|
||||||
instance = instance,
|
|
||||||
onProgress = { pct ->
|
|
||||||
runOnUiThread {
|
|
||||||
binding.progressBar.progress = pct
|
|
||||||
binding.tvPercent.text = "$pct%"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
result.fold(
|
|
||||||
onSuccess = { url -> showSuccess(url) },
|
|
||||||
onFailure = { err -> showError(err.message ?: "Upload failed") }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun processUrlUpload(instance: Instance, url: String) {
|
|
||||||
binding.tvStatus.text = "Uploading to ${instance.name}..."
|
|
||||||
binding.progressBar.visibility = View.VISIBLE
|
|
||||||
binding.progressBar.isIndeterminate = true
|
|
||||||
binding.tvPercent.text = ""
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
val result = uploader.uploadUrl(url, instance)
|
|
||||||
result.fold(
|
|
||||||
onSuccess = { postUrl -> showSuccess(postUrl) },
|
|
||||||
onFailure = { err -> showError(err.message ?: "URL Upload failed") }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showSuccess(url: String) {
|
|
||||||
uploadedUrl = url
|
|
||||||
copyToClipboard(url)
|
|
||||||
|
|
||||||
binding.progressBar.visibility = View.GONE
|
|
||||||
binding.tvPercent.text = "100%"
|
|
||||||
binding.tvStatus.text = "Upload complete! Link copied to clipboard."
|
|
||||||
binding.tvResultUrl.visibility = View.VISIBLE
|
|
||||||
binding.tvResultUrl.text = url
|
|
||||||
binding.layoutButtons.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
Toast.makeText(this, "Uploaded! Link copied to clipboard.", Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showError(msg: String) {
|
|
||||||
binding.progressBar.visibility = View.GONE
|
|
||||||
binding.tvStatus.text = "Error: $msg"
|
|
||||||
binding.btnClose.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun copyToClipboard(text: String) {
|
|
||||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
||||||
val clip = ClipData.newPlainText("fuggloader link", text)
|
|
||||||
clipboard.setPrimaryClip(clip)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class UploaderService(private val prefs: PreferencesManager) {
|
|||||||
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
onProgress(100)
|
onProgress(100)
|
||||||
val postUrl = parsePostUrl(responseBody)
|
val postUrl = parsePostUrl(responseBody, targetInstance.resultLinkType)
|
||||||
Result.success(postUrl)
|
Result.success(postUrl)
|
||||||
} else {
|
} else {
|
||||||
Log.e("UploaderService", "Upload failed: HTTP ${response.code}, Body: $responseBody")
|
Log.e("UploaderService", "Upload failed: HTTP ${response.code}, Body: $responseBody")
|
||||||
@@ -134,7 +134,7 @@ class UploaderService(private val prefs: PreferencesManager) {
|
|||||||
val responseBody = response.body?.string() ?: ""
|
val responseBody = response.body?.string() ?: ""
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val postUrl = parsePostUrl(responseBody)
|
val postUrl = parsePostUrl(responseBody, targetInstance.resultLinkType)
|
||||||
Result.success(postUrl)
|
Result.success(postUrl)
|
||||||
} else {
|
} else {
|
||||||
Log.e("UploaderService", "URL Upload failed: HTTP ${response.code}, Body: $responseBody")
|
Log.e("UploaderService", "URL Upload failed: HTTP ${response.code}, Body: $responseBody")
|
||||||
@@ -172,23 +172,63 @@ class UploaderService(private val prefs: PreferencesManager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parsePostUrl(jsonResponse: String): String {
|
private fun parsePostUrl(jsonResponse: String, linkType: String): String {
|
||||||
|
Log.d("UploaderService", "Parsing response (linkType=$linkType): $jsonResponse")
|
||||||
return try {
|
return try {
|
||||||
val json = JSONObject(jsonResponse)
|
val json = JSONObject(jsonResponse)
|
||||||
if (json.has("url")) {
|
|
||||||
json.getString("url")
|
// For f0ckm API:
|
||||||
} else if (json.has("post_url")) {
|
// "url" is the viewer post link (e.g. https://fuggloader.com/abcdef)
|
||||||
json.getString("post_url")
|
// "file_url" is the direct link (e.g. https://fuggloader.com/b/uuid.jpg)
|
||||||
} else if (json.has("link")) {
|
|
||||||
json.getString("link")
|
if (linkType == "direct") {
|
||||||
|
if (json.has("file_url") && !json.isNull("file_url")) {
|
||||||
|
return json.getString("file_url")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
jsonResponse
|
if (json.has("url") && !json.isNull("url")) {
|
||||||
|
return json.getString("url")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback logic for other potential APIs or if preferred key is missing
|
||||||
|
val keysToTry = if (linkType == "direct") {
|
||||||
|
listOf("file_url", "direct_url", "link", "url", "post_url")
|
||||||
|
} else {
|
||||||
|
listOf("url", "post_url", "link", "file_url")
|
||||||
|
}
|
||||||
|
|
||||||
|
findValue(json, keysToTry) ?: jsonResponse
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e("UploaderService", "Error parsing JSON", e)
|
||||||
jsonResponse
|
jsonResponse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun findValue(json: JSONObject, keys: List<String>): String? {
|
||||||
|
// First try root level
|
||||||
|
for (key in keys) {
|
||||||
|
if (json.has(key) && !json.isNull(key)) {
|
||||||
|
val value = json.optString(key)
|
||||||
|
if (value.isNotBlank()) return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then try common nested objects like "data" or "image"
|
||||||
|
val nestedObjects = listOf("data", "image", "success") // some use "success" as a wrapper
|
||||||
|
for (nested in nestedObjects) {
|
||||||
|
if (json.has(nested) && !json.isNull(nested)) {
|
||||||
|
val obj = json.optJSONObject(nested)
|
||||||
|
if (obj != null) {
|
||||||
|
val found = findValue(obj, keys)
|
||||||
|
if (found != null) return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
private fun getFileName(contentResolver: ContentResolver, uri: Uri): String? {
|
private fun getFileName(contentResolver: ContentResolver, uri: Uri): String? {
|
||||||
var result: String? = null
|
var result: String? = null
|
||||||
if (uri.scheme == "content") {
|
if (uri.scheme == "content") {
|
||||||
|
|||||||
@@ -190,6 +190,23 @@
|
|||||||
android:background="#1E1E24"
|
android:background="#1E1E24"
|
||||||
android:padding="8dp" />
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
<!-- Result Link Type -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Result Link Type"
|
||||||
|
android:textColor="#E0E0E0"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerLinkType"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:background="#1E1E24"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -48,15 +48,12 @@
|
|||||||
android:textColor="#808090"
|
android:textColor="#808090"
|
||||||
android:textSize="12sp" />
|
android:textSize="12sp" />
|
||||||
|
|
||||||
<TextView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/tvResultUrl"
|
android:id="@+id/rvResults"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:background="#121216"
|
android:maxHeight="300dp"
|
||||||
android:padding="8dp"
|
|
||||||
android:textColor="#A5D6A7"
|
|
||||||
android:textSize="13sp"
|
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -69,21 +66,11 @@
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btnCopy"
|
android:id="@+id/btnCopy"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="4dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:backgroundTint="#2E2E3A"
|
android:backgroundTint="#2E2E3A"
|
||||||
android:text="Copy Link" />
|
android:text="Copy Link" />
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnOpen"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:backgroundTint="#6200EE"
|
|
||||||
android:text="Open Post" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
58
android/app/src/main/res/layout/item_upload_result.xml
Normal file
58
android/app/src/main/res/layout/item_upload_result.xml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvFileName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:ellipsize="middle"
|
||||||
|
android:singleLine="true" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvFileUrl"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#A5D6A7"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:singleLine="true" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnItemCopy"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minWidth="0dp"
|
||||||
|
android:paddingHorizontal="8dp"
|
||||||
|
android:text="Copy"
|
||||||
|
android:textColor="#B0B0C0"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnItemOpen"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:minWidth="0dp"
|
||||||
|
android:paddingHorizontal="8dp"
|
||||||
|
android:text="Open"
|
||||||
|
android:textColor="#B0B0C0"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
62
gui.py
62
gui.py
@@ -18,7 +18,7 @@ import signal
|
|||||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
|
||||||
from PySide6.QtCore import Qt, QThread, QObject, Signal, QUrl, QFile, QIODevice, QTimer, QMimeData, QProcess
|
from PySide6.QtCore import Qt, QThread, QObject, Signal, QUrl, QFile, QIODevice, QTimer, QMimeData, QProcess
|
||||||
from PySide6.QtGui import QIcon, QAction, QPixmap, QPainter, QColor, QFont, QPen, QImage, QDesktopServices, QDrag, QClipboard
|
from PySide6.QtGui import QIcon, QAction, QActionGroup, QPixmap, QPainter, QColor, QFont, QPen, QImage, QDesktopServices, QDrag, QClipboard
|
||||||
from PySide6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QHttpMultiPart, QHttpPart, QNetworkReply, QLocalServer, QLocalSocket
|
from PySide6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QHttpMultiPart, QHttpPart, QNetworkReply, QLocalServer, QLocalSocket
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QApplication, QSystemTrayIcon, QMenu, QDialog, QVBoxLayout, QHBoxLayout,
|
QApplication, QSystemTrayIcon, QMenu, QDialog, QVBoxLayout, QHBoxLayout,
|
||||||
@@ -688,21 +688,29 @@ class SettingsDialog(QDialog):
|
|||||||
self.btn_toggle_key.setText("Show")
|
self.btn_toggle_key.setText("Show")
|
||||||
|
|
||||||
def refresh_instance_combo(self):
|
def refresh_instance_combo(self):
|
||||||
|
prev_ignore = self._ignore_instance_signals
|
||||||
self._ignore_instance_signals = True
|
self._ignore_instance_signals = True
|
||||||
self.cb_instance.clear()
|
try:
|
||||||
for inst in self.instances:
|
self.cb_instance.clear()
|
||||||
self.cb_instance.addItem(inst.get("name", "Unnamed Instance"))
|
for inst in self.instances:
|
||||||
|
self.cb_instance.addItem(inst.get("name", "Unnamed Instance"))
|
||||||
|
|
||||||
if 0 <= self.current_instance_index < len(self.instances):
|
if 0 <= self.current_instance_index < len(self.instances):
|
||||||
self.cb_instance.setCurrentIndex(self.current_instance_index)
|
self.cb_instance.setCurrentIndex(self.current_instance_index)
|
||||||
self._ignore_instance_signals = False
|
self.load_instance_fields(self.current_instance_index)
|
||||||
self.load_instance_fields(self.current_instance_index)
|
finally:
|
||||||
|
self._ignore_instance_signals = prev_ignore
|
||||||
|
|
||||||
def load_instance_fields(self, index):
|
def load_instance_fields(self, index):
|
||||||
if 0 <= index < len(self.instances):
|
if 0 <= index < len(self.instances):
|
||||||
inst = self.instances[index]
|
prev_ignore = self._ignore_instance_signals
|
||||||
self.txt_url.setText(inst.get("api_url", ""))
|
self._ignore_instance_signals = True
|
||||||
self.txt_key.setText(inst.get("api_key", ""))
|
try:
|
||||||
|
inst = self.instances[index]
|
||||||
|
self.txt_url.setText(inst.get("api_url", ""))
|
||||||
|
self.txt_key.setText(inst.get("api_key", ""))
|
||||||
|
finally:
|
||||||
|
self._ignore_instance_signals = prev_ignore
|
||||||
|
|
||||||
def on_instance_changed(self, new_index):
|
def on_instance_changed(self, new_index):
|
||||||
if self._ignore_instance_signals or new_index < 0 or new_index >= len(self.instances):
|
if self._ignore_instance_signals or new_index < 0 or new_index >= len(self.instances):
|
||||||
@@ -1715,11 +1723,11 @@ class SystemTrayApp(QObject):
|
|||||||
self.menu.addAction(self.abort_action)
|
self.menu.addAction(self.abort_action)
|
||||||
|
|
||||||
settings_action = QAction("Settings...", self.menu)
|
settings_action = QAction("Settings...", self.menu)
|
||||||
settings_action.triggered.connect(self.on_open_settings)
|
settings_action.triggered.connect(lambda checked=False: self.on_open_settings())
|
||||||
self.menu.addAction(settings_action)
|
self.menu.addAction(settings_action)
|
||||||
|
|
||||||
gallery_action = QAction("Recent Uploads Gallery", self.menu)
|
gallery_action = QAction("Recent Uploads Gallery", self.menu)
|
||||||
gallery_action.triggered.connect(self.on_open_gallery)
|
gallery_action.triggered.connect(lambda checked=False: self.on_open_gallery())
|
||||||
self.menu.addAction(gallery_action)
|
self.menu.addAction(gallery_action)
|
||||||
|
|
||||||
self.menu.addSeparator()
|
self.menu.addSeparator()
|
||||||
@@ -1747,6 +1755,8 @@ class SystemTrayApp(QObject):
|
|||||||
|
|
||||||
def rebuild_instance_menu(self):
|
def rebuild_instance_menu(self):
|
||||||
self.instance_menu.clear()
|
self.instance_menu.clear()
|
||||||
|
self.instance_actions = []
|
||||||
|
|
||||||
config = get_env_config()
|
config = get_env_config()
|
||||||
instances = config.get("instances", [])
|
instances = config.get("instances", [])
|
||||||
active_idx = config.get("active_instance_index", 0)
|
active_idx = config.get("active_instance_index", 0)
|
||||||
@@ -1754,22 +1764,27 @@ class SystemTrayApp(QObject):
|
|||||||
active_name = config.get("active_instance_name", "Default Instance")
|
active_name = config.get("active_instance_name", "Default Instance")
|
||||||
self.instance_menu.setTitle(f"Instance: {active_name}")
|
self.instance_menu.setTitle(f"Instance: {active_name}")
|
||||||
|
|
||||||
|
self.instance_action_group = QActionGroup(self.instance_menu)
|
||||||
|
self.instance_action_group.setExclusive(True)
|
||||||
|
|
||||||
for idx, inst in enumerate(instances):
|
for idx, inst in enumerate(instances):
|
||||||
name = inst.get("name", f"Instance {idx + 1}")
|
name = inst.get("name", f"Instance {idx + 1}")
|
||||||
url = inst.get("api_url", "")
|
url = inst.get("api_url", "")
|
||||||
action = QAction(name, self.instance_menu)
|
action = QAction(name, self.instance_menu)
|
||||||
|
action.setCheckable(True)
|
||||||
|
self.instance_action_group.addAction(action)
|
||||||
if url:
|
if url:
|
||||||
action.setToolTip(url)
|
action.setToolTip(url)
|
||||||
action.setCheckable(True)
|
|
||||||
if idx == active_idx:
|
if idx == active_idx:
|
||||||
action.setChecked(True)
|
action.setChecked(True)
|
||||||
|
|
||||||
action.triggered.connect(lambda checked=False, i=idx: self.switch_active_instance(i))
|
action.triggered.connect(lambda checked=False, i=idx: self.switch_active_instance(i))
|
||||||
self.instance_menu.addAction(action)
|
self.instance_menu.addAction(action)
|
||||||
|
self.instance_actions.append(action)
|
||||||
|
|
||||||
self.instance_menu.addSeparator()
|
self.instance_menu.addSeparator()
|
||||||
manage_action = QAction("Manage Instances...", self.instance_menu)
|
manage_action = QAction("Manage Instances...", self.instance_menu)
|
||||||
manage_action.triggered.connect(self.on_open_settings)
|
manage_action.triggered.connect(lambda checked=False: self.on_open_settings())
|
||||||
self.instance_menu.addAction(manage_action)
|
self.instance_menu.addAction(manage_action)
|
||||||
|
|
||||||
def switch_active_instance(self, index):
|
def switch_active_instance(self, index):
|
||||||
@@ -1785,8 +1800,12 @@ class SystemTrayApp(QObject):
|
|||||||
name = active_inst.get("name", "Instance")
|
name = active_inst.get("name", "Instance")
|
||||||
print(f"[f0ckm-gui] Switched active instance to '{name}' ({active_inst.get('api_url', '')})", flush=True)
|
print(f"[f0ckm-gui] Switched active instance to '{name}' ({active_inst.get('api_url', '')})", flush=True)
|
||||||
self.show_message("f0ckm Instance Switched", f"Active instance changed to: {name}", QSystemTrayIcon.Information)
|
self.show_message("f0ckm Instance Switched", f"Active instance changed to: {name}", QSystemTrayIcon.Information)
|
||||||
self.rebuild_instance_menu()
|
|
||||||
if self.settings_dialog and self.settings_dialog.isVisible():
|
self.instance_menu.setTitle(f"Instance: {name}")
|
||||||
|
if hasattr(self, "instance_actions") and 0 <= index < len(self.instance_actions):
|
||||||
|
self.instance_actions[index].setChecked(True)
|
||||||
|
|
||||||
|
if self.settings_dialog:
|
||||||
self.settings_dialog.load_current_settings()
|
self.settings_dialog.load_current_settings()
|
||||||
|
|
||||||
def enqueue_targets(self, targets):
|
def enqueue_targets(self, targets):
|
||||||
@@ -1847,10 +1866,12 @@ class SystemTrayApp(QObject):
|
|||||||
print(f"[f0ckm-gui] Notification clicked -> Opening URL in browser: '{url}'", flush=True)
|
print(f"[f0ckm-gui] Notification clicked -> Opening URL in browser: '{url}'", flush=True)
|
||||||
QDesktopServices.openUrl(QUrl(url))
|
QDesktopServices.openUrl(QUrl(url))
|
||||||
|
|
||||||
def on_open_gallery(self):
|
def on_open_gallery(self, checked=False):
|
||||||
if self.gallery_window is None:
|
if self.gallery_window is None:
|
||||||
self.gallery_window = GalleryWindow()
|
self.gallery_window = GalleryWindow()
|
||||||
self.gallery_window.load_items()
|
self.gallery_window.load_items()
|
||||||
|
if self.gallery_window.isMinimized():
|
||||||
|
self.gallery_window.showNormal()
|
||||||
self.gallery_window.show()
|
self.gallery_window.show()
|
||||||
self.gallery_window.raise_()
|
self.gallery_window.raise_()
|
||||||
self.gallery_window.activateWindow()
|
self.gallery_window.activateWindow()
|
||||||
@@ -1889,9 +1910,12 @@ class SystemTrayApp(QObject):
|
|||||||
|
|
||||||
self.tray.setIcon(self.default_icon)
|
self.tray.setIcon(self.default_icon)
|
||||||
|
|
||||||
def on_open_settings(self):
|
def on_open_settings(self, checked=False):
|
||||||
if not self.settings_dialog:
|
if not self.settings_dialog:
|
||||||
self.settings_dialog = SettingsDialog(tray_app=self)
|
self.settings_dialog = SettingsDialog(tray_app=self)
|
||||||
|
self.settings_dialog.load_current_settings()
|
||||||
|
if self.settings_dialog.isMinimized():
|
||||||
|
self.settings_dialog.showNormal()
|
||||||
self.settings_dialog.show()
|
self.settings_dialog.show()
|
||||||
self.settings_dialog.raise_()
|
self.settings_dialog.raise_()
|
||||||
self.settings_dialog.activateWindow()
|
self.settings_dialog.activateWindow()
|
||||||
|
|||||||
Reference in New Issue
Block a user