fix desktop app and android app
This commit is contained in:
@@ -10,7 +10,8 @@ data class Instance(
|
||||
var apiKey: String,
|
||||
var defaultRating: String = "none",
|
||||
var defaultTags: String = "",
|
||||
var defaultVisibility: String = "0"
|
||||
var defaultVisibility: String = "0",
|
||||
var resultLinkType: String = "post"
|
||||
) {
|
||||
fun toJsonObject(): JSONObject {
|
||||
return JSONObject().apply {
|
||||
@@ -21,6 +22,7 @@ data class Instance(
|
||||
put("defaultRating", defaultRating)
|
||||
put("defaultTags", defaultTags)
|
||||
put("defaultVisibility", defaultVisibility)
|
||||
put("resultLinkType", resultLinkType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +37,8 @@ data class Instance(
|
||||
apiKey = json.optString("apiKey", ""),
|
||||
defaultRating = json.optString("defaultRating", "none"),
|
||||
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()
|
||||
setupVisibilitySpinner()
|
||||
setupLinkTypeSpinner()
|
||||
setupInstanceSpinner()
|
||||
|
||||
binding.btnSave.setOnClickListener {
|
||||
@@ -63,6 +64,12 @@ class MainActivity : AppCompatActivity() {
|
||||
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() {
|
||||
currentInstances = prefs.getInstances()
|
||||
if (currentInstances.isEmpty()) {
|
||||
@@ -106,6 +113,12 @@ class MainActivity : AppCompatActivity() {
|
||||
"2" -> binding.spinnerVisibility.setSelection(2)
|
||||
else -> binding.spinnerVisibility.setSelection(0)
|
||||
}
|
||||
|
||||
if (instance.resultLinkType == "direct") {
|
||||
binding.spinnerLinkType.setSelection(1)
|
||||
} else {
|
||||
binding.spinnerLinkType.setSelection(0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveSettings() {
|
||||
@@ -125,6 +138,8 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
currentInstance.defaultVisibility = binding.spinnerVisibility.selectedItemPosition.toString()
|
||||
|
||||
currentInstance.resultLinkType = if (binding.spinnerLinkType.selectedItemPosition == 1) "direct" else "post"
|
||||
|
||||
prefs.updateInstance(currentInstance)
|
||||
|
||||
// Refresh spinner to show updated name
|
||||
|
||||
@@ -6,11 +6,16 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.OpenableColumns
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
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.ItemUploadResultBinding
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -21,7 +26,10 @@ class ShareActivity : AppCompatActivity() {
|
||||
private lateinit var prefs: PreferencesManager
|
||||
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?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -31,6 +39,8 @@ class ShareActivity : AppCompatActivity() {
|
||||
prefs = PreferencesManager(this)
|
||||
uploader = UploaderService(prefs)
|
||||
|
||||
setupRecyclerView()
|
||||
|
||||
if (!prefs.isConfigured()) {
|
||||
Toast.makeText(this, "fuggloader is not configured. Please open app settings first.", Toast.LENGTH_LONG).show()
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
@@ -40,17 +50,10 @@ class ShareActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
binding.btnCopy.setOnClickListener {
|
||||
uploadedUrl?.let { url ->
|
||||
copyToClipboard(url)
|
||||
Toast.makeText(this, "URL 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()
|
||||
if (uploadedResults.isNotEmpty()) {
|
||||
val text = uploadedResults.joinToString("\n") { it.url }
|
||||
copyToClipboard(text)
|
||||
Toast.makeText(this, "All URLs copied to clipboard!", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,65 +64,214 @@ class ShareActivity : AppCompatActivity() {
|
||||
prepareShare(intent)
|
||||
}
|
||||
|
||||
private fun prepareShare(intent: Intent) {
|
||||
val instances = prefs.getInstances()
|
||||
if (instances.size > 1) {
|
||||
val names = instances.map { it.name }.toTypedArray()
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Select Instance")
|
||||
.setItems(names) { _, which ->
|
||||
handleShareIntent(instances[which], intent)
|
||||
private fun setupRecyclerView() {
|
||||
resultAdapter = UploadResultAdapter(uploadedResults) { result, action ->
|
||||
when (action) {
|
||||
ResultAction.COPY -> {
|
||||
copyToClipboard(result.url)
|
||||
Toast.makeText(this, "URL copied!", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
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) {
|
||||
val action = intent.action
|
||||
val type = intent.type
|
||||
|
||||
val uris = mutableListOf<Uri>()
|
||||
var sharedUrl: String? = null
|
||||
|
||||
if (Intent.ACTION_SEND == action && type != null) {
|
||||
if ("text/plain" == type) {
|
||||
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
if (!sharedText.isNull_or_blank_url()) {
|
||||
val targetUrl = extractUrl(sharedText!!)
|
||||
processUrlUpload(instance, targetUrl)
|
||||
sharedUrl = extractUrl(sharedText!!)
|
||||
} else {
|
||||
val streamUri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
if (streamUri != null) {
|
||||
processFileUpload(instance, streamUri)
|
||||
} else {
|
||||
showError("No valid file or URL found in share intent")
|
||||
uris.add(streamUri)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val streamUri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
if (streamUri != null) {
|
||||
processFileUpload(instance, streamUri)
|
||||
} else {
|
||||
showError("No stream URI found for file share")
|
||||
uris.add(streamUri)
|
||||
}
|
||||
}
|
||||
} else if (Intent.ACTION_SEND_MULTIPLE == action) {
|
||||
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
if (!uris.isNullOrEmpty()) {
|
||||
// Upload first file in batch for now
|
||||
processFileUpload(instance, uris[0])
|
||||
} else {
|
||||
showError("No files found in multi-share intent")
|
||||
val streamUris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
if (streamUris != null) {
|
||||
uris.addAll(streamUris)
|
||||
}
|
||||
} 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 {
|
||||
@@ -138,71 +290,4 @@ class ShareActivity : AppCompatActivity() {
|
||||
}
|
||||
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) {
|
||||
onProgress(100)
|
||||
val postUrl = parsePostUrl(responseBody)
|
||||
val postUrl = parsePostUrl(responseBody, targetInstance.resultLinkType)
|
||||
Result.success(postUrl)
|
||||
} else {
|
||||
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() ?: ""
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val postUrl = parsePostUrl(responseBody)
|
||||
val postUrl = parsePostUrl(responseBody, targetInstance.resultLinkType)
|
||||
Result.success(postUrl)
|
||||
} else {
|
||||
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 {
|
||||
val json = JSONObject(jsonResponse)
|
||||
if (json.has("url")) {
|
||||
json.getString("url")
|
||||
} else if (json.has("post_url")) {
|
||||
json.getString("post_url")
|
||||
} else if (json.has("link")) {
|
||||
json.getString("link")
|
||||
|
||||
// For f0ckm API:
|
||||
// "url" is the viewer post link (e.g. https://fuggloader.com/abcdef)
|
||||
// "file_url" is the direct link (e.g. https://fuggloader.com/b/uuid.jpg)
|
||||
|
||||
if (linkType == "direct") {
|
||||
if (json.has("file_url") && !json.isNull("file_url")) {
|
||||
return json.getString("file_url")
|
||||
}
|
||||
} 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) {
|
||||
Log.e("UploaderService", "Error parsing JSON", e)
|
||||
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? {
|
||||
var result: String? = null
|
||||
if (uri.scheme == "content") {
|
||||
|
||||
@@ -190,6 +190,23 @@
|
||||
android:background="#1E1E24"
|
||||
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 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -48,15 +48,12 @@
|
||||
android:textColor="#808090"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvResultUrl"
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvResults"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="#121216"
|
||||
android:padding="8dp"
|
||||
android:textColor="#A5D6A7"
|
||||
android:textSize="13sp"
|
||||
android:maxHeight="300dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
@@ -69,21 +66,11 @@
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCopy"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_weight="1"
|
||||
android:backgroundTint="#2E2E3A"
|
||||
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>
|
||||
|
||||
<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>
|
||||
64
gui.py
64
gui.py
@@ -18,7 +18,7 @@ import signal
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
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.QtWidgets import (
|
||||
QApplication, QSystemTrayIcon, QMenu, QDialog, QVBoxLayout, QHBoxLayout,
|
||||
@@ -688,21 +688,29 @@ class SettingsDialog(QDialog):
|
||||
self.btn_toggle_key.setText("Show")
|
||||
|
||||
def refresh_instance_combo(self):
|
||||
prev_ignore = self._ignore_instance_signals
|
||||
self._ignore_instance_signals = True
|
||||
self.cb_instance.clear()
|
||||
for inst in self.instances:
|
||||
self.cb_instance.addItem(inst.get("name", "Unnamed Instance"))
|
||||
try:
|
||||
self.cb_instance.clear()
|
||||
for inst in self.instances:
|
||||
self.cb_instance.addItem(inst.get("name", "Unnamed Instance"))
|
||||
|
||||
if 0 <= self.current_instance_index < len(self.instances):
|
||||
self.cb_instance.setCurrentIndex(self.current_instance_index)
|
||||
self._ignore_instance_signals = False
|
||||
self.load_instance_fields(self.current_instance_index)
|
||||
if 0 <= self.current_instance_index < len(self.instances):
|
||||
self.cb_instance.setCurrentIndex(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):
|
||||
if 0 <= index < len(self.instances):
|
||||
inst = self.instances[index]
|
||||
self.txt_url.setText(inst.get("api_url", ""))
|
||||
self.txt_key.setText(inst.get("api_key", ""))
|
||||
prev_ignore = self._ignore_instance_signals
|
||||
self._ignore_instance_signals = True
|
||||
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):
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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.addSeparator()
|
||||
@@ -1747,6 +1755,8 @@ class SystemTrayApp(QObject):
|
||||
|
||||
def rebuild_instance_menu(self):
|
||||
self.instance_menu.clear()
|
||||
self.instance_actions = []
|
||||
|
||||
config = get_env_config()
|
||||
instances = config.get("instances", [])
|
||||
active_idx = config.get("active_instance_index", 0)
|
||||
@@ -1754,22 +1764,27 @@ class SystemTrayApp(QObject):
|
||||
active_name = config.get("active_instance_name", "Default Instance")
|
||||
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):
|
||||
name = inst.get("name", f"Instance {idx + 1}")
|
||||
url = inst.get("api_url", "")
|
||||
action = QAction(name, self.instance_menu)
|
||||
action.setCheckable(True)
|
||||
self.instance_action_group.addAction(action)
|
||||
if url:
|
||||
action.setToolTip(url)
|
||||
action.setCheckable(True)
|
||||
if idx == active_idx:
|
||||
action.setChecked(True)
|
||||
|
||||
action.triggered.connect(lambda checked=False, i=idx: self.switch_active_instance(i))
|
||||
self.instance_menu.addAction(action)
|
||||
self.instance_actions.append(action)
|
||||
|
||||
self.instance_menu.addSeparator()
|
||||
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)
|
||||
|
||||
def switch_active_instance(self, index):
|
||||
@@ -1785,8 +1800,12 @@ class SystemTrayApp(QObject):
|
||||
name = active_inst.get("name", "Instance")
|
||||
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.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()
|
||||
|
||||
def enqueue_targets(self, targets):
|
||||
@@ -1847,14 +1866,16 @@ class SystemTrayApp(QObject):
|
||||
print(f"[f0ckm-gui] Notification clicked -> Opening URL in browser: '{url}'", flush=True)
|
||||
QDesktopServices.openUrl(QUrl(url))
|
||||
|
||||
def on_open_gallery(self):
|
||||
def on_open_gallery(self, checked=False):
|
||||
if self.gallery_window is None:
|
||||
self.gallery_window = GalleryWindow()
|
||||
self.gallery_window.load_items()
|
||||
if self.gallery_window.isMinimized():
|
||||
self.gallery_window.showNormal()
|
||||
self.gallery_window.show()
|
||||
self.gallery_window.raise_()
|
||||
self.gallery_window.activateWindow()
|
||||
|
||||
|
||||
def show(self):
|
||||
self.tray.show()
|
||||
|
||||
@@ -1889,9 +1910,12 @@ class SystemTrayApp(QObject):
|
||||
|
||||
self.tray.setIcon(self.default_icon)
|
||||
|
||||
def on_open_settings(self):
|
||||
def on_open_settings(self, checked=False):
|
||||
if not self.settings_dialog:
|
||||
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.raise_()
|
||||
self.settings_dialog.activateWindow()
|
||||
|
||||
Reference in New Issue
Block a user