diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 01c1422..767ac57 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -49,52 +49,6 @@ - - - - - - - - - - - - - - - - - - - - - - - { - - private final List files; - private final Context context; - private final Set selectedPositions = new HashSet<>(); - private OnSelectionChangedListener selectionChangedListener; - - /** - * Constructor - * - * @param context The context - * @param files List of dumpstate files - */ - public DumpstateAdapter(Context context, List files) { - this.context = context; - this.files = files; - } - - /** - * Set a listener for selection changes - * - * @param listener The listener - */ - public void setOnSelectionChangedListener(OnSelectionChangedListener listener) { - this.selectionChangedListener = listener; - } - - /** - * Get the list of selected files - * - * @return List of selected files - */ - public List getSelectedFiles() { - List selectedFiles = new ArrayList<>(); - for (int position : selectedPositions) { - if (position >= 0 && position < files.size()) { - selectedFiles.add(files.get(position)); - } - } - return selectedFiles; - } - - /** - * Check if an item is selected - * - * @param position The position to check - * @return true if selected, false otherwise - */ - public boolean isSelected(int position) { - return selectedPositions.contains(position); - } - - /** - * Select or deselect an item - * - * @param position The position to select/deselect - * @param selected Whether to select (true) or deselect (false) - */ - public void setSelected(int position, boolean selected) { - if (selected) { - selectedPositions.add(position); - } else { - selectedPositions.remove(position); - } - notifyItemChanged(position); - - if (selectionChangedListener != null) { - selectionChangedListener.onSelectionChanged(selectedPositions.size()); - } - } - - /** - * Toggle selection state of an item - * - * @param position The position to toggle - */ - public void toggleSelection(int position) { - setSelected(position, !isSelected(position)); - } - - /** - * Select all items - */ - public void selectAll() { - selectedPositions.clear(); - for (int i = 0; i < files.size(); i++) { - selectedPositions.add(i); - } - notifyDataSetChanged(); - - if (selectionChangedListener != null) { - selectionChangedListener.onSelectionChanged(selectedPositions.size()); - } - } - - /** - * Clear all selections - */ - public void clearSelections() { - selectedPositions.clear(); - notifyDataSetChanged(); - - if (selectionChangedListener != null) { - selectionChangedListener.onSelectionChanged(0); - } - } - - /** - * Get the number of selected items - * - * @return The count of selected items - */ - public int getSelectedCount() { - return selectedPositions.size(); - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_dumpstate_file, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - File file = files.get(position); - - // Set file name - holder.fileNameText.setText(file.getName()); - - // Add tooltip for long filenames - holder.fileNameText.setOnLongClickListener(v -> { - Toast.makeText(context, file.getName(), Toast.LENGTH_LONG).show(); - return true; - }); - - // Format and set file date - Date lastModified = new Date(file.lastModified()); - String formattedDate = DateFormat.format("MMM dd, yyyy hh:mm a", lastModified).toString(); - holder.fileDateText.setText(context.getString(R.string.dumpstate_date_format, formattedDate)); - - // Format and set file size - String fileSize = Formatter.formatFileSize(context, file.length()); - holder.fileSizeText.setText(context.getString(R.string.dumpstate_size_format, fileSize)); - - // Set checkbox state - holder.checkBox.setChecked(isSelected(position)); - - // Set click listeners - holder.checkBox.setOnClickListener(v -> toggleSelection(holder.getAbsoluteAdapterPosition())); - - holder.itemView.setOnClickListener(v -> toggleSelection(holder.getAbsoluteAdapterPosition())); - } - - @Override - public int getItemCount() { - return files.size(); - } - - /** - * ViewHolder for dumpstate file items - */ - static class ViewHolder extends RecyclerView.ViewHolder { - final CheckBox checkBox; - final TextView fileNameText; - final TextView fileDateText; - final TextView fileSizeText; - - ViewHolder(View itemView) { - super(itemView); - checkBox = itemView.findViewById(R.id.checkBox); - fileNameText = itemView.findViewById(R.id.fileNameText); - fileDateText = itemView.findViewById(R.id.fileDateText); - fileSizeText = itemView.findViewById(R.id.fileSizeText); - } - } - - /** - * Interface for handling selection changes - */ - public interface OnSelectionChangedListener { - void onSelectionChanged(int selectedCount); - } -} diff --git a/app/src/main/java/com/mattintech/simplelogupload/adapter/DumpstateFileAdapter.java b/app/src/main/java/com/mattintech/simplelogupload/adapter/DumpstateFileAdapter.java deleted file mode 100644 index 537e7d0..0000000 --- a/app/src/main/java/com/mattintech/simplelogupload/adapter/DumpstateFileAdapter.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.mattintech.simplelogupload.adapter; - -import android.content.Context; -import android.text.format.DateFormat; -import android.text.format.Formatter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.mattintech.simplelogupload.R; - -import java.io.File; -import java.util.Date; -import java.util.List; - -/** - * Adapter for displaying dumpstate files in a RecyclerView - */ -public class DumpstateFileAdapter extends RecyclerView.Adapter { - - private final List files; - private final Context context; - private int selectedPosition = -1; - private OnItemSelectedListener listener; - - /** - * Constructor - * - * @param context The context - * @param files List of dumpstate files - */ - public DumpstateFileAdapter(Context context, List files) { - this.context = context; - this.files = files; - } - - /** - * Set a listener for item selection - * - * @param listener The listener - */ - public void setOnItemSelectedListener(OnItemSelectedListener listener) { - this.listener = listener; - } - - /** - * Get the currently selected file - * - * @return The selected file or null if none selected - */ - public File getSelectedFile() { - if (selectedPosition >= 0 && selectedPosition < files.size()) { - return files.get(selectedPosition); - } - return null; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_dumpstate_file, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - File file = files.get(position); - - // Set file name - holder.fileNameText.setText(file.getName()); - - // Add tooltip for long filenames - holder.fileNameText.setOnLongClickListener(v -> { - Toast.makeText(context, file.getName(), Toast.LENGTH_LONG).show(); - return true; - }); - - // Format and set file date - Date lastModified = new Date(file.lastModified()); - String formattedDate = DateFormat.format("MMM dd, yyyy hh:mm a", lastModified).toString(); - holder.fileDateText.setText(context.getString(R.string.dumpstate_date_format, formattedDate)); - - // Format and set file size - String fileSize = Formatter.formatFileSize(context, file.length()); - holder.fileSizeText.setText(context.getString(R.string.dumpstate_size_format, fileSize)); - - // Set checkbox state - holder.checkBox.setChecked(position == selectedPosition); - - // Set click listener for the whole item - holder.itemView.setOnClickListener(v -> { - int previousSelected = selectedPosition; - selectedPosition = holder.getAdapterPosition(); - - // Update previous selected item - if (previousSelected >= 0) { - notifyItemChanged(previousSelected); - } - - // Update newly selected item - notifyItemChanged(selectedPosition); - - // Notify listener - if (listener != null) { - listener.onItemSelected(file); - } - }); - } - - @Override - public int getItemCount() { - return files.size(); - } - - /** - * ViewHolder for dumpstate file items - */ - static class ViewHolder extends RecyclerView.ViewHolder { - final CheckBox checkBox; - final TextView fileNameText; - final TextView fileDateText; - final TextView fileSizeText; - - ViewHolder(View itemView) { - super(itemView); - checkBox = itemView.findViewById(R.id.checkBox); - fileNameText = itemView.findViewById(R.id.fileNameText); - fileDateText = itemView.findViewById(R.id.fileDateText); - fileSizeText = itemView.findViewById(R.id.fileSizeText); - } - } - - /** - * Interface for handling item selection - */ - public interface OnItemSelectedListener { - void onItemSelected(File file); - } -} diff --git a/app/src/main/java/com/mattintech/simplelogupload/service/FileUploadForegroundService.java b/app/src/main/java/com/mattintech/simplelogupload/service/FileUploadForegroundService.java index 3521751..0ea222f 100644 --- a/app/src/main/java/com/mattintech/simplelogupload/service/FileUploadForegroundService.java +++ b/app/src/main/java/com/mattintech/simplelogupload/service/FileUploadForegroundService.java @@ -24,7 +24,7 @@ import com.mattintech.simplelogupload.controller.FileController; import com.mattintech.simplelogupload.controller.UploadController; import com.mattintech.simplelogupload.model.UploadResult; import com.mattintech.simplelogupload.upload.UploadStrategySelector; -import com.mattintech.simplelogupload.view.MainActivity; +import com.mattintech.simplelogupload.ComposeMainActivity; import java.io.File; @@ -198,7 +198,7 @@ public class FileUploadForegroundService extends Service { * @return The notification */ private Notification createInitialNotification() { - Intent notificationIntent = new Intent(this, MainActivity.class); + Intent notificationIntent = new Intent(this, ComposeMainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE); diff --git a/app/src/main/java/com/mattintech/simplelogupload/view/DumpstateSelectionDialog.java b/app/src/main/java/com/mattintech/simplelogupload/view/DumpstateSelectionDialog.java deleted file mode 100644 index bdaf098..0000000 --- a/app/src/main/java/com/mattintech/simplelogupload/view/DumpstateSelectionDialog.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.mattintech.simplelogupload.view; - -import android.app.Dialog; -import android.content.Context; -import android.os.Bundle; -import android.view.View; -import android.view.Window; -import android.widget.Button; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.mattintech.simplelogupload.R; -import com.mattintech.simplelogupload.adapter.DumpstateFileAdapter; - -import java.io.File; -import java.util.Arrays; -import java.util.List; - -/** - * Dialog for selecting a dumpstate file from multiple options - */ -public class DumpstateSelectionDialog extends Dialog { - - private final List dumpstateFiles; - private final OnDumpstateSelectedListener listener; - - private TextView titleText; - private RecyclerView recyclerView; - private TextView emptyView; - private Button selectButton; - private Button cancelButton; - - private DumpstateFileAdapter adapter; - - /** - * Interface for handling dumpstate selection - */ - public interface OnDumpstateSelectedListener { - void onDumpstateSelected(File dumpstateFile); - } - - /** - * Constructor - * - * @param context The context - * @param dumpstateFiles List of dumpstate files - * @param listener Listener for selection events - */ - public DumpstateSelectionDialog(@NonNull Context context, - File[] dumpstateFiles, - OnDumpstateSelectedListener listener) { - super(context); - this.dumpstateFiles = Arrays.asList(dumpstateFiles); - this.listener = listener; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_NO_TITLE); - setContentView(R.layout.dialog_dumpstate_selection); - - // Initialize views - titleText = findViewById(R.id.titleText); - recyclerView = findViewById(R.id.dumpstateRecyclerView); - emptyView = findViewById(R.id.emptyView); - selectButton = findViewById(R.id.selectButton); - cancelButton = findViewById(R.id.cancelButton); - - // Update title with file count - titleText.setText(getContext().getString(R.string.dumpstate_selection_subtitle, dumpstateFiles.size())); - - // Set up RecyclerView - setupRecyclerView(); - - // Set up buttons - cancelButton.setOnClickListener(v -> dismiss()); - selectButton.setOnClickListener(v -> { - File selectedFile = adapter.getSelectedFile(); - if (selectedFile != null && listener != null) { - listener.onDumpstateSelected(selectedFile); - dismiss(); - } - }); - - // Show empty view if no files - if (dumpstateFiles.isEmpty()) { - recyclerView.setVisibility(View.GONE); - emptyView.setVisibility(View.VISIBLE); - } else { - recyclerView.setVisibility(View.VISIBLE); - emptyView.setVisibility(View.GONE); - } - } - - /** - * Set up the RecyclerView with the adapter - */ - private void setupRecyclerView() { - recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - adapter = new DumpstateFileAdapter(getContext(), dumpstateFiles); - recyclerView.setAdapter(adapter); - - // Listen for item selection to enable/disable select button - adapter.setOnItemSelectedListener(file -> { - selectButton.setEnabled(true); - }); - } -} diff --git a/app/src/main/java/com/mattintech/simplelogupload/view/MainActivity.java b/app/src/main/java/com/mattintech/simplelogupload/view/MainActivity.java deleted file mode 100644 index f0e6a35..0000000 --- a/app/src/main/java/com/mattintech/simplelogupload/view/MainActivity.java +++ /dev/null @@ -1,732 +0,0 @@ -package com.mattintech.simplelogupload.view; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.appcompat.app.AppCompatActivity; -import androidx.cardview.widget.CardView; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import com.mattintech.simplelogupload.R; -import com.mattintech.simplelogupload.adapter.DumpstateAdapter; -import com.mattintech.simplelogupload.constant.AppConstants; -import com.mattintech.simplelogupload.controller.FileController; -import com.mattintech.simplelogupload.controller.ShareIntentHandler; -import com.mattintech.simplelogupload.controller.UploadController; -import com.mattintech.simplelogupload.service.FileUploadForegroundService; -import com.mattintech.simplelogupload.util.PermissionUtil; -import com.mattintech.simplelogupload.util.PreferencesUtil; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Main activity for the application - */ -public class MainActivity extends AppCompatActivity { - private static final String TAG = AppConstants.APP_TAG + "MainActivity"; - - private TextView dumpstateStatusText; - private TextView selectedFileText; - private Button selectFileButton; - private Button uploadDumpstateButton; - private Button uploadSelectedFileButton; - private ProgressBar progressBar; - private CardView selectedFileCard; - - // Dumpstate multi-select components - private RecyclerView dumpstateRecyclerView; - private Button toggleDumpstateListButton; - private Button selectAllButton; - private Button clearSelectionsButton; - private LinearLayout multiSelectButtonsLayout; - private DumpstateAdapter dumpstateAdapter; - - private File selectedFile; - private File dumpstateFile; - - private UploadController uploadController; - - // BroadcastReceiver to listen for upload results - private BroadcastReceiver uploadReceiver; - - // Activity result launcher for file picking - private ActivityResultLauncher filePickerLauncher; - - // Flag to track if dumpstate list is expanded - private boolean isDumpstateListExpanded = false; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Check if setup is required - if (!PreferencesUtil.isSetupCompleted(this) || !PreferencesUtil.areServerSettingsConfigured(this)) { - // Navigate to welcome activity - Intent intent = new Intent(this, WelcomeActivity.class); - startActivity(intent); - finish(); - return; - } - - // Set content view - setContentView(R.layout.activity_main); - - uploadController = new UploadController(this); - - // Initialize UI elements - dumpstateStatusText = findViewById(R.id.dumpstateStatusText); - selectedFileText = findViewById(R.id.selectedFileText); - selectFileButton = findViewById(R.id.selectFileButton); - uploadDumpstateButton = findViewById(R.id.uploadDumpstateButton); - uploadSelectedFileButton = findViewById(R.id.uploadSelectedFileButton); - progressBar = findViewById(R.id.progressBar); - selectedFileCard = findViewById(R.id.selectedFileCard); - - // Initialize multi-select UI elements - dumpstateRecyclerView = findViewById(R.id.dumpstateRecyclerView); - toggleDumpstateListButton = findViewById(R.id.toggleDumpstateListButton); - selectAllButton = findViewById(R.id.selectAllButton); - clearSelectionsButton = findViewById(R.id.clearSelectionsButton); - multiSelectButtonsLayout = findViewById(R.id.multiSelectButtonsLayout); - - // Set up RecyclerView - dumpstateRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - - // Set up button click listeners - selectFileButton.setOnClickListener(v -> openFilePicker()); - uploadDumpstateButton.setOnClickListener(v -> uploadDumpstate()); - uploadSelectedFileButton.setOnClickListener(v -> uploadSelectedFile()); - toggleDumpstateListButton.setOnClickListener(v -> toggleDumpstateList()); - selectAllButton.setOnClickListener(v -> selectAllDumpstates()); - clearSelectionsButton.setOnClickListener(v -> clearDumpstateSelections()); - - // Initialize file picker launcher - filePickerLauncher = registerForActivityResult( - new ActivityResultContracts.GetContent(), - uri -> { - if (uri != null) { - handleFileSelection(uri); - } - }); - - // Check for necessary permissions - checkAndRequestPermissions(); - - // Initialize broadcast receiver - setupBroadcastReceiver(); - - // Handle intent (e.g., from share) - handleIntent(getIntent()); - } - - /** - * Toggle the visibility of the dumpstate list - */ - private void toggleDumpstateList() { - isDumpstateListExpanded = !isDumpstateListExpanded; - - if (isDumpstateListExpanded) { - // Show the list - loadDumpstateFiles(); - dumpstateRecyclerView.setVisibility(View.VISIBLE); - multiSelectButtonsLayout.setVisibility(View.VISIBLE); - toggleDumpstateListButton.setText(R.string.hide_dumpstates); - - // Change the upload button to handle multiple files - updateUploadButtonForMultiSelect(); - } else { - // Hide the list - dumpstateRecyclerView.setVisibility(View.GONE); - multiSelectButtonsLayout.setVisibility(View.GONE); - toggleDumpstateListButton.setText(R.string.show_dumpstates); - - // Restore normal upload button - uploadDumpstateButton.setText(R.string.upload_dumpstate_button); - uploadDumpstateButton.setOnClickListener(v -> uploadDumpstate()); - } - } - - /** - * Load dumpstate files into the RecyclerView - */ - private void loadDumpstateFiles() { - File[] dumpstateFiles = FileController.getAllDumpStateFiles(); - - if (dumpstateFiles.length > 0) { - List fileList = new ArrayList<>(Arrays.asList(dumpstateFiles)); - - if (dumpstateAdapter == null) { - dumpstateAdapter = new DumpstateAdapter(this, fileList); - dumpstateAdapter.setOnSelectionChangedListener(this::onDumpstateSelectionChanged); - dumpstateRecyclerView.setAdapter(dumpstateAdapter); - } else { - // Just update the adapter with new files - dumpstateAdapter = new DumpstateAdapter(this, fileList); - dumpstateAdapter.setOnSelectionChangedListener(this::onDumpstateSelectionChanged); - dumpstateRecyclerView.setAdapter(dumpstateAdapter); - } - } - } - - /** - * Handle selection changes in the dumpstate list - * - * @param selectedCount The number of selected items - */ - private void onDumpstateSelectionChanged(int selectedCount) { - // Update the upload button text - if (selectedCount > 0) { - uploadDumpstateButton.setText(getString(R.string.upload_multiple_dumpstates, selectedCount)); - uploadDumpstateButton.setEnabled(true); - } else { - uploadDumpstateButton.setText(R.string.upload_dumpstate_button); - uploadDumpstateButton.setEnabled(false); - } - } - - /** - * Update the upload button to handle multiple files - */ - private void updateUploadButtonForMultiSelect() { - uploadDumpstateButton.setOnClickListener(v -> uploadSelectedDumpstates()); - - // Initially disable until files are selected - uploadDumpstateButton.setEnabled(dumpstateAdapter != null && dumpstateAdapter.getSelectedCount() > 0); - - // Update button text if there are already selections - if (dumpstateAdapter != null && dumpstateAdapter.getSelectedCount() > 0) { - uploadDumpstateButton.setText(getString(R.string.upload_multiple_dumpstates, - dumpstateAdapter.getSelectedCount())); - } else { - uploadDumpstateButton.setText(R.string.upload_dumpstate_button); - } - } - - /** - * Select all dumpstate files - */ - private void selectAllDumpstates() { - if (dumpstateAdapter != null) { - dumpstateAdapter.selectAll(); - } - } - - /** - * Clear all dumpstate selections - */ - private void clearDumpstateSelections() { - if (dumpstateAdapter != null) { - dumpstateAdapter.clearSelections(); - } - } - - /** - * Upload selected dumpstate files - */ - private void uploadSelectedDumpstates() { - if (dumpstateAdapter == null || dumpstateAdapter.getSelectedCount() == 0) { - Toast.makeText(this, R.string.no_dumpstates_selected, Toast.LENGTH_SHORT).show(); - return; - } - - List selectedFiles = dumpstateAdapter.getSelectedFiles(); - int fileCount = selectedFiles.size(); - - // Show upload parameters dialog first - new UploadParamsDialog(this, new UploadParamsDialog.UploadParamsListener() { - @Override - public void onUploadParamsSet(int maxDownloads, int expiryHours) { - // Update UI - progressBar.setVisibility(View.VISIBLE); - uploadDumpstateButton.setEnabled(false); - dumpstateStatusText.setText(getString(R.string.preparing_multiple_files, fileCount)); - - // Prepare and upload multiple files - FileController.prepareMultipleFilesForUploadAsync(selectedFiles, MainActivity.this) - .thenAccept(zipFile -> { - Log.d(TAG, "Multiple files zipped successfully: " + zipFile.getAbsolutePath()); - - runOnUiThread(() -> { - dumpstateStatusText.setText(getString(R.string.uploading_multiple_files, fileCount)); - }); - - // Upload the zip file with parameters - startUploadService(zipFile, maxDownloads, expiryHours); - }) - .exceptionally(ex -> { - Log.e(TAG, "Error preparing multiple files", ex); - runOnUiThread(() -> { - progressBar.setVisibility(View.GONE); - uploadDumpstateButton.setEnabled(true); - Toast.makeText(MainActivity.this, - getString(R.string.file_error, ex.getMessage()), - Toast.LENGTH_LONG).show(); - }); - return null; - }); - } - - @Override - public void onUploadParamsCancelled() { - // User cancelled, do nothing - } - }).show(); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - setIntent(intent); - handleIntent(intent); - } - - /** - * Handle the intent, particularly for share intents - * - * @param intent The intent to handle - */ - private void handleIntent(Intent intent) { - if (intent == null) { - return; - } - - // Check if this is a share intent - if (ShareIntentHandler.isShareIntent(intent)) { - handleShareIntent(intent); - } - } - - /** - * Handle a share intent - * - * @param intent The share intent - */ - private void handleShareIntent(Intent intent) { - List sharedUris = ShareIntentHandler.extractSharedUris(intent); - - if (sharedUris.isEmpty()) { - Toast.makeText(this, "No files shared", Toast.LENGTH_SHORT).show(); - return; - } - - // For now, just handle the first URI - Uri sharedUri = sharedUris.get(0); - - progressBar.setVisibility(View.VISIBLE); - - // Process the shared URI - ShareIntentHandler.processSharedUri(this, sharedUri) - .thenAccept(file -> { - // Save the file for upload - updateSelectedFile(file); - runOnUiThread(() -> { - progressBar.setVisibility(View.GONE); - }); - }) - .exceptionally(ex -> { - Log.e(TAG, "Error processing shared file", ex); - runOnUiThread(() -> { - Toast.makeText(MainActivity.this, - getString(R.string.file_error, ex.getMessage()), - Toast.LENGTH_LONG).show(); - progressBar.setVisibility(View.GONE); - }); - return null; - }); - } - - /** - * Opens the file picker - */ - private void openFilePicker() { - filePickerLauncher.launch("*/*"); - } - - /** - * Handle file selection from file picker - * - * @param uri The URI of the selected file - */ - private void handleFileSelection(Uri uri) { - progressBar.setVisibility(View.VISIBLE); - - // Process the URI - ShareIntentHandler.processSharedUri(this, uri) - .thenAccept(file -> { - // Save the file for upload - updateSelectedFile(file); - runOnUiThread(() -> { - progressBar.setVisibility(View.GONE); - }); - }) - .exceptionally(ex -> { - Log.e(TAG, "Error processing selected file", ex); - runOnUiThread(() -> { - Toast.makeText(MainActivity.this, - getString(R.string.file_error, ex.getMessage()), - Toast.LENGTH_LONG).show(); - progressBar.setVisibility(View.GONE); - }); - return null; - }); - } - - /** - * Upload the selected file - */ - private void uploadSelectedFile() { - if (selectedFile == null || !selectedFile.exists()) { - Toast.makeText(this, R.string.no_file_selected, Toast.LENGTH_SHORT).show(); - return; - } - - // Show upload parameters dialog - showUploadParamsDialog(selectedFile); - } - - /** - * Upload the dumpstate file - */ - private void uploadDumpstate() { - // Get all dumpstate files - File[] dumpstateFiles = FileController.getAllDumpStateFiles(); - - if (dumpstateFiles.length == 0) { - Toast.makeText(this, R.string.no_dumpstate_found, Toast.LENGTH_SHORT).show(); - return; - } - - if (dumpstateFiles.length == 1) { - // Only one dumpstate file, use it directly - dumpstateFile = dumpstateFiles[0]; - showUploadParamsDialog(dumpstateFile); - } else { - // Multiple dumpstate files are available, suggest expanding the list - if (!isDumpstateListExpanded) { - toggleDumpstateList(); - } else { - // If list is already expanded, use the first file - dumpstateFile = dumpstateFiles[0]; - showUploadParamsDialog(dumpstateFile); - } - } - } - - /** - * Start uploading the selected dumpstate file - * - * @param file The dumpstate file to upload - * @param maxDownloads The maximum number of downloads - * @param expiryHours The expiry time in hours - */ - private void startDumpstateUpload(File file, int maxDownloads, int expiryHours) { - if (file == null || !file.exists()) { - Toast.makeText(this, R.string.no_dumpstate_found, Toast.LENGTH_SHORT).show(); - return; - } - - // Update UI - progressBar.setVisibility(View.VISIBLE); - uploadDumpstateButton.setEnabled(false); - dumpstateStatusText.setText(getString(R.string.uploading_file, file.getName())); - - // Start the upload service with parameters - startUploadService(file, maxDownloads, expiryHours); - } - - /** - * Start the upload service with the given file and parameters - * - * @param file The file to upload - * @param maxDownloads The maximum number of downloads - * @param expiryHours The expiry time in hours - */ - private void startUploadService(File file, int maxDownloads, int expiryHours) { - // Start the upload service - Intent serviceIntent = new Intent(this, FileUploadForegroundService.class); - serviceIntent.putExtra(FileUploadForegroundService.EXTRA_FILE_PATH, file.getAbsolutePath()); - serviceIntent.putExtra(FileUploadForegroundService.EXTRA_MAX_DOWNLOADS, maxDownloads); - serviceIntent.putExtra(FileUploadForegroundService.EXTRA_EXPIRY_HOURS, expiryHours); - startService(serviceIntent); - } - - /** - * Show the upload parameters dialog - * - * @param file The file to upload - */ - private void showUploadParamsDialog(File file) { - new UploadParamsDialog(this, new UploadParamsDialog.UploadParamsListener() { - @Override - public void onUploadParamsSet(int maxDownloads, int expiryHours) { - // Update UI - progressBar.setVisibility(View.VISIBLE); - uploadSelectedFileButton.setEnabled(false); - - if (file == selectedFile) { - selectedFileText.setText(getString(R.string.uploading_file, file.getName())); - } else if (file == dumpstateFile) { - dumpstateStatusText.setText(getString(R.string.uploading_file, file.getName())); - uploadDumpstateButton.setEnabled(false); - } - - // Start the upload service with parameters - startUploadService(file, maxDownloads, expiryHours); - } - - @Override - public void onUploadParamsCancelled() { - // User cancelled, do nothing - } - }).show(); - } - - /** - * Update the selected file UI - * - * @param file The selected file - */ - private void updateSelectedFile(File file) { - selectedFile = file; - runOnUiThread(() -> { - selectedFileCard.setVisibility(View.VISIBLE); - selectedFileText.setText(getString(R.string.selected_file, file.getName())); - uploadSelectedFileButton.setVisibility(View.VISIBLE); - uploadSelectedFileButton.setEnabled(true); - }); - } - - /** - * Set up the broadcast receiver for upload results - */ - private void setupBroadcastReceiver() { - uploadReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - Log.d(TAG, "Received broadcast: " + intent.getAction()); - progressBar.setVisibility(View.GONE); - uploadSelectedFileButton.setEnabled(true); - uploadDumpstateButton.setEnabled(true); - - if (intent.getAction().equals(AppConstants.ACTION_UPLOAD_COMPLETE)) { - String uploadCode = intent.getStringExtra(AppConstants.EXTRA_UPLOAD_CODE); - Log.d(TAG, "Upload complete with code: " + uploadCode); - showUploadSuccessDialog(uploadCode); - } else if (intent.getAction().equals(AppConstants.ACTION_UPLOAD_ERROR)) { - String errorMessage = intent.getStringExtra(AppConstants.EXTRA_UPLOAD_ERROR); - Log.e(TAG, "Upload error: " + errorMessage); - Toast.makeText(MainActivity.this, - getString(R.string.upload_error, errorMessage), - Toast.LENGTH_LONG).show(); - resetUI(); - } - } - }; - } - - /** - * Reset the UI after upload completion or error - */ - private void resetUI() { - // Get all dumpstate files - File[] dumpstateFiles = FileController.getAllDumpStateFiles(); - - if (dumpstateFiles.length > 0) { - if (dumpstateFiles.length == 1) { - // Only one dumpstate file - dumpstateFile = dumpstateFiles[0]; - dumpstateStatusText.setText(getString(R.string.dumpstate_found, dumpstateFile.getName())); - } else { - // Multiple dumpstate files - dumpstateStatusText.setText(getString(R.string.multiple_dumpstates_found) + - " (" + dumpstateFiles.length + ")"); - - // If we have a specific file selected, show it - if (dumpstateFile != null && dumpstateFile.exists()) { - dumpstateStatusText.append("\n" + dumpstateFile.getName()); - } else { - // Otherwise show the most recent one - dumpstateFile = dumpstateFiles[0]; - dumpstateStatusText.append("\n" + dumpstateFile.getName()); - } - } - uploadDumpstateButton.setEnabled(true); - - // Update multiselect if active - if (isDumpstateListExpanded && dumpstateAdapter != null) { - loadDumpstateFiles(); - updateUploadButtonForMultiSelect(); - } - } else { - dumpstateFile = null; - dumpstateStatusText.setText(R.string.no_dumpstate_found); - uploadDumpstateButton.setEnabled(false); - } - - if (selectedFile != null) { - selectedFileText.setText(getString(R.string.selected_file, selectedFile.getName())); - } - } - - @Override - protected void onResume() { - super.onResume(); - - // Check for file if we have permissions - if (PermissionUtil.hasAllFilesAccessPermission()) { - checkForDumpStateAndUpdateUI(); - } - - // Register the broadcast receiver - IntentFilter filter = new IntentFilter(); - filter.addAction(AppConstants.ACTION_UPLOAD_COMPLETE); - filter.addAction(AppConstants.ACTION_UPLOAD_ERROR); - - // Register with both regular and local broadcast managers to ensure delivery - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { - registerReceiver(uploadReceiver, filter, Context.RECEIVER_NOT_EXPORTED); - } else { - registerReceiver(uploadReceiver, filter); - } - - // Also register with LocalBroadcastManager for reliability - LocalBroadcastManager.getInstance(this).registerReceiver(uploadReceiver, filter); - } - - @Override - protected void onPause() { - super.onPause(); - - // Unregister the broadcast receiver - try { - unregisterReceiver(uploadReceiver); - } catch (IllegalArgumentException e) { - // Receiver not registered, ignore - } - - // Also unregister from LocalBroadcastManager - LocalBroadcastManager.getInstance(this).unregisterReceiver(uploadReceiver); - } - - /** - * Check for and request necessary permissions - */ - private void checkAndRequestPermissions() { - // Request notification permission - PermissionUtil.requestNotificationPermission(this); - - // Check and request all files access permission - if (!PermissionUtil.hasAllFilesAccessPermission()) { - PermissionUtil.requestAllFilesAccessPermission(this); - } - } - - /** - * Check for dumpstate files and update the UI - */ - private void checkForDumpStateAndUpdateUI() { - // Look for dumpstate files - File[] dumpstateFiles = FileController.getAllDumpStateFiles(); - - if (dumpstateFiles.length > 0) { - if (dumpstateFiles.length == 1) { - // Only one dumpstate file found, use it directly - dumpstateFile = dumpstateFiles[0]; - dumpstateStatusText.setText(getString(R.string.dumpstate_found, dumpstateFile.getName())); - uploadDumpstateButton.setEnabled(true); - } else { - // Multiple dumpstate files found - dumpstateStatusText.setText(getString(R.string.multiple_dumpstates_found) + - " (" + dumpstateFiles.length + ")"); - - // Set the most recent one as default - dumpstateFile = dumpstateFiles[0]; - // Show the dumpstate name in a new line - dumpstateStatusText.append("\n" + dumpstateFile.getName()); - uploadDumpstateButton.setEnabled(true); - - // Update toggle button visibility - toggleDumpstateListButton.setVisibility(View.VISIBLE); - } - } else { - dumpstateFile = null; - dumpstateStatusText.setText(R.string.no_dumpstate_found); - uploadDumpstateButton.setEnabled(false); - toggleDumpstateListButton.setVisibility(View.GONE); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode == PermissionUtil.REQUEST_MANAGE_EXTERNAL_STORAGE) { - if (PermissionUtil.hasAllFilesAccessPermission()) { - checkForDumpStateAndUpdateUI(); - } else { - Toast.makeText(this, R.string.permission_not_granted, Toast.LENGTH_SHORT).show(); - } - } - } - - /** - * Show a dialog with the upload success result - * - * @param uploadCode The upload code - */ - private void showUploadSuccessDialog(String uploadCode) { - Log.d(TAG, "Opening upload success dialog with code: " + uploadCode); - runOnUiThread(() -> { - try { - UploadResultDialog dialog = new UploadResultDialog(this, uploadCode); - dialog.show(); - } catch (Exception e) { - Log.e(TAG, "Error showing upload result dialog", e); - Toast.makeText(this, getString(R.string.upload_success, uploadCode), Toast.LENGTH_LONG).show(); - } - }); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.main_menu, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - - if (id == R.id.action_history) { - // Open upload history activity - startActivity(new Intent(this, UploadHistoryActivity.class)); - return true; - } else if (id == R.id.action_settings) { - // Open settings activity - startActivity(new Intent(this, SettingsActivity.class)); - return true; - } - - return super.onOptionsItemSelected(item); - } -} diff --git a/app/src/main/java/com/mattintech/simplelogupload/view/ServerSetupActivity.java b/app/src/main/java/com/mattintech/simplelogupload/view/ServerSetupActivity.java deleted file mode 100644 index 89844e8..0000000 --- a/app/src/main/java/com/mattintech/simplelogupload/view/ServerSetupActivity.java +++ /dev/null @@ -1,206 +0,0 @@ -package com.mattintech.simplelogupload.view; - -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.text.TextUtils; -import android.util.Log; -import android.widget.Button; -import android.widget.Toast; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.appcompat.app.AppCompatActivity; - -import com.google.android.material.textfield.TextInputEditText; -import com.journeyapps.barcodescanner.ScanContract; -import com.journeyapps.barcodescanner.ScanOptions; -import com.journeyapps.barcodescanner.ScanIntentResult; -import com.mattintech.simplelogupload.R; -import com.mattintech.simplelogupload.constant.AppConstants; -import com.mattintech.simplelogupload.model.QrServerConfig; -import com.mattintech.simplelogupload.util.PermissionUtil; -import com.mattintech.simplelogupload.util.PreferencesUtil; -import com.mattintech.simplelogupload.util.QrScannerUtil; - -/** - * Activity for server setup - */ -public class ServerSetupActivity extends AppCompatActivity { - private static final String TAG = AppConstants.APP_TAG + "ServerSetupActivity"; - - private TextInputEditText serverAddressInput; - private TextInputEditText serverPortInput; - private TextInputEditText clientKeyInput; - private ActivityResultLauncher qrScannerLauncher; - private Button scanQrButton; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_server_setup); - - // Initialize UI components - serverAddressInput = findViewById(R.id.serverAddressInput); - serverPortInput = findViewById(R.id.serverPortInput); - clientKeyInput = findViewById(R.id.clientKeyInput); - Button finishSetupButton = findViewById(R.id.finishSetupButton); - scanQrButton = findViewById(R.id.scanQrButton); - - // Initialize QR scanner launcher - qrScannerLauncher = registerForActivityResult( - new ScanContract(), - result -> handleQrScanResult(result) - ); - - // Set up QR scan button click listener - scanQrButton.setOnClickListener(v -> initiateQrScan()); - - // Set up action bar - if (getSupportActionBar() != null) { - getSupportActionBar().setTitle("Server Setup"); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } - - // Set up button click listener - finishSetupButton.setOnClickListener(v -> saveSettings()); - } - - /** - * Save settings to SharedPreferences - */ - private void saveSettings() { - try { - // Get values from input fields - String serverAddress = serverAddressInput.getText().toString().trim(); - String serverPortStr = serverPortInput.getText().toString().trim(); - String clientKey = clientKeyInput.getText().toString().trim(); - - // Validate input - if (TextUtils.isEmpty(serverAddress)) { - serverAddressInput.setError("Server address is required"); - return; - } - - if (TextUtils.isEmpty(serverPortStr)) { - serverPortInput.setError("Server port is required"); - return; - } - - int serverPort; - try { - serverPort = Integer.parseInt(serverPortStr); - if (serverPort <= 0 || serverPort > 65535) { - serverPortInput.setError("Port must be between 1 and 65535"); - return; - } - } catch (NumberFormatException e) { - serverPortInput.setError("Invalid port number"); - return; - } - - if (TextUtils.isEmpty(clientKey)) { - clientKeyInput.setError("Client key is required"); - return; - } - - // Save settings - PreferencesUtil.setServerAddress(this, serverAddress); - PreferencesUtil.setServerPort(this, serverPort); - PreferencesUtil.setClientKey(this, clientKey); - PreferencesUtil.setSetupCompleted(this, true); - - Log.d(TAG, "Server setup completed successfully"); - Toast.makeText(this, "Setup completed successfully!", Toast.LENGTH_SHORT).show(); - - // Navigate to main activity - Intent intent = new Intent(ServerSetupActivity.this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivity(intent); - finish(); - - } catch (Exception e) { - Log.e(TAG, "Error saving settings", e); - Toast.makeText(this, "Error saving settings: " + e.getMessage(), Toast.LENGTH_LONG).show(); - } - } - - /** - * Initiate QR code scan - */ - private void initiateQrScan() { - // Check camera permission - if (!PermissionUtil.hasCameraPermission(this)) { - PermissionUtil.requestCameraPermission(this); - return; - } - - // Launch scanner - ScanOptions options = QrScannerUtil.createScanOptions(); - qrScannerLauncher.launch(options); - } - - /** - * Handle QR scan result - * - * @param result The scan result - */ - private void handleQrScanResult(ScanIntentResult result) { - QrScannerUtil.processScanResult( - result.getContents(), - new QrScannerUtil.QrScanCallback() { - @Override - public void onQrScanned(QrServerConfig config) { - // Auto-fill the input fields - serverAddressInput.setText(config.getServer()); - serverPortInput.setText(String.valueOf(config.getPort())); - clientKeyInput.setText(config.getKey()); - - Toast.makeText(ServerSetupActivity.this, - R.string.qr_scan_success, - Toast.LENGTH_SHORT).show(); - - Log.d(TAG, "QR code scanned successfully"); - } - - @Override - public void onQrScanCancelled() { - Toast.makeText(ServerSetupActivity.this, - R.string.qr_scan_cancelled, - Toast.LENGTH_SHORT).show(); - } - - @Override - public void onQrScanError(String errorMessage) { - Toast.makeText(ServerSetupActivity.this, - getString(R.string.qr_scan_error, errorMessage), - Toast.LENGTH_LONG).show(); - Log.e(TAG, "QR scan error: " + errorMessage); - } - } - ); - } - - /** - * Handle permission request results - */ - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - - if (requestCode == PermissionUtil.REQUEST_CAMERA_PERMISSION) { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // Permission granted, initiate scan - initiateQrScan(); - } else { - // Permission denied - Toast.makeText(this, R.string.camera_permission_denied, Toast.LENGTH_LONG).show(); - } - } - } - - @Override - public boolean onSupportNavigateUp() { - onBackPressed(); - return true; - } -} diff --git a/app/src/main/java/com/mattintech/simplelogupload/view/SettingsActivity.java b/app/src/main/java/com/mattintech/simplelogupload/view/SettingsActivity.java deleted file mode 100644 index e9178de..0000000 --- a/app/src/main/java/com/mattintech/simplelogupload/view/SettingsActivity.java +++ /dev/null @@ -1,230 +0,0 @@ -package com.mattintech.simplelogupload.view; - -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.text.TextUtils; -import android.util.Log; -import android.widget.Button; -import android.widget.Toast; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.appcompat.app.AppCompatActivity; - -import com.google.android.material.textfield.TextInputEditText; -import com.journeyapps.barcodescanner.ScanContract; -import com.journeyapps.barcodescanner.ScanOptions; -import com.journeyapps.barcodescanner.ScanIntentResult; -import com.mattintech.simplelogupload.R; -import com.mattintech.simplelogupload.constant.AppConstants; -import com.mattintech.simplelogupload.model.QrServerConfig; -import com.mattintech.simplelogupload.util.PermissionUtil; -import com.mattintech.simplelogupload.util.PreferencesUtil; -import com.mattintech.simplelogupload.util.QrScannerUtil; - -/** - * Activity for application settings - */ -public class SettingsActivity extends AppCompatActivity { - private static final String TAG = AppConstants.APP_TAG + "SettingsActivity"; - - private TextInputEditText serverAddressInput; - private TextInputEditText serverPortInput; - private TextInputEditText clientKeyInput; - private ActivityResultLauncher qrScannerLauncher; - private Button scanQrButton; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_settings); - - // Initialize UI components - serverAddressInput = findViewById(R.id.serverAddressInput); - serverPortInput = findViewById(R.id.serverPortInput); - clientKeyInput = findViewById(R.id.clientKeyInput); - Button saveButton = findViewById(R.id.saveSettingsButton); - Button resetButton = findViewById(R.id.resetSettingsButton); - scanQrButton = findViewById(R.id.scanQrButton); - - // Initialize QR scanner launcher - qrScannerLauncher = registerForActivityResult( - new ScanContract(), - result -> handleQrScanResult(result) - ); - - // Set up QR scan button click listener - scanQrButton.setOnClickListener(v -> initiateQrScan()); - - // Set up action bar - if (getSupportActionBar() != null) { - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle("Settings"); - } - - // Load current settings - loadSettings(); - - // Set up button click listeners - saveButton.setOnClickListener(v -> saveSettings()); - resetButton.setOnClickListener(v -> resetSettings()); - } - - /** - * Load current settings from SharedPreferences - */ - private void loadSettings() { - String serverAddress = PreferencesUtil.getServerAddress(this); - int serverPort = PreferencesUtil.getServerPort(this); - String clientKey = PreferencesUtil.getClientKey(this); - - serverAddressInput.setText(serverAddress); - if (serverPort > 0) { - serverPortInput.setText(String.valueOf(serverPort)); - } else { - serverPortInput.setText(""); - } - clientKeyInput.setText(clientKey); - } - - /** - * Save settings to SharedPreferences - */ - private void saveSettings() { - try { - // Get values from input fields - String serverAddress = serverAddressInput.getText().toString().trim(); - String serverPortStr = serverPortInput.getText().toString().trim(); - String clientKey = clientKeyInput.getText().toString().trim(); - - // Validate input - if (TextUtils.isEmpty(serverAddress)) { - serverAddressInput.setError("Server address is required"); - return; - } - - if (TextUtils.isEmpty(serverPortStr)) { - serverPortInput.setError("Server port is required"); - return; - } - - int serverPort; - try { - serverPort = Integer.parseInt(serverPortStr); - if (serverPort <= 0 || serverPort > 65535) { - serverPortInput.setError("Port must be between 1 and 65535"); - return; - } - } catch (NumberFormatException e) { - serverPortInput.setError("Invalid port number"); - return; - } - - if (TextUtils.isEmpty(clientKey)) { - clientKeyInput.setError("Client key is required"); - return; - } - - // Save settings - PreferencesUtil.setServerAddress(this, serverAddress); - PreferencesUtil.setServerPort(this, serverPort); - PreferencesUtil.setClientKey(this, clientKey); - - Log.d(TAG, "Settings saved successfully"); - Toast.makeText(this, "Settings saved successfully", Toast.LENGTH_SHORT).show(); - finish(); - - } catch (Exception e) { - Log.e(TAG, "Error saving settings", e); - Toast.makeText(this, "Error saving settings: " + e.getMessage(), Toast.LENGTH_LONG).show(); - } - } - - /** - * Reset settings to default values - */ - private void resetSettings() { - PreferencesUtil.resetToDefaults(this); - loadSettings(); - Toast.makeText(this, "Settings reset to defaults", Toast.LENGTH_SHORT).show(); - } - - /** - * Initiate QR code scan - */ - private void initiateQrScan() { - // Check camera permission - if (!PermissionUtil.hasCameraPermission(this)) { - PermissionUtil.requestCameraPermission(this); - return; - } - - // Launch scanner - ScanOptions options = QrScannerUtil.createScanOptions(); - qrScannerLauncher.launch(options); - } - - /** - * Handle QR scan result - * - * @param result The scan result - */ - private void handleQrScanResult(ScanIntentResult result) { - QrScannerUtil.processScanResult( - result.getContents(), - new QrScannerUtil.QrScanCallback() { - @Override - public void onQrScanned(QrServerConfig config) { - // Auto-fill the input fields - serverAddressInput.setText(config.getServer()); - serverPortInput.setText(String.valueOf(config.getPort())); - clientKeyInput.setText(config.getKey()); - - Toast.makeText(SettingsActivity.this, - R.string.qr_scan_success, - Toast.LENGTH_SHORT).show(); - - Log.d(TAG, "QR code scanned successfully"); - } - - @Override - public void onQrScanCancelled() { - Toast.makeText(SettingsActivity.this, - R.string.qr_scan_cancelled, - Toast.LENGTH_SHORT).show(); - } - - @Override - public void onQrScanError(String errorMessage) { - Toast.makeText(SettingsActivity.this, - getString(R.string.qr_scan_error, errorMessage), - Toast.LENGTH_LONG).show(); - Log.e(TAG, "QR scan error: " + errorMessage); - } - } - ); - } - - /** - * Handle permission request results - */ - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - - if (requestCode == PermissionUtil.REQUEST_CAMERA_PERMISSION) { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // Permission granted, initiate scan - initiateQrScan(); - } else { - // Permission denied - Toast.makeText(this, R.string.camera_permission_denied, Toast.LENGTH_LONG).show(); - } - } - } - - @Override - public boolean onSupportNavigateUp() { - onBackPressed(); - return true; - } -} diff --git a/app/src/main/java/com/mattintech/simplelogupload/view/UploadHistoryActivity.java b/app/src/main/java/com/mattintech/simplelogupload/view/UploadHistoryActivity.java deleted file mode 100644 index 652189f..0000000 --- a/app/src/main/java/com/mattintech/simplelogupload/view/UploadHistoryActivity.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.mattintech.simplelogupload.view; - -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.os.Bundle; -import android.util.Log; -import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.appcompat.app.AlertDialog; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.Observer; -import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.mattintech.simplelogupload.R; -import com.mattintech.simplelogupload.constant.AppConstants; -import com.mattintech.simplelogupload.model.UploadHistory; -import com.mattintech.simplelogupload.view.adapter.UploadHistoryAdapter; -import com.mattintech.simplelogupload.viewmodel.UploadHistoryViewModel; - -import java.util.List; - -/** - * Activity for displaying upload history - */ -public class UploadHistoryActivity extends AppCompatActivity implements UploadHistoryAdapter.OnHistoryItemClickListener { - private static final String TAG = AppConstants.APP_TAG + "HistoryActivity"; - - private UploadHistoryViewModel viewModel; - private UploadHistoryAdapter adapter; - private RecyclerView recyclerView; - private TextView emptyView; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_upload_history); - - // Enable "up" navigation - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - // Initialize views - recyclerView = findViewById(R.id.historyRecyclerView); - emptyView = findViewById(R.id.emptyView); - - // Set up the RecyclerView - recyclerView.setLayoutManager(new LinearLayoutManager(this)); - adapter = new UploadHistoryAdapter(this); - recyclerView.setAdapter(adapter); - - // Initialize the ViewModel - viewModel = new ViewModelProvider.AndroidViewModelFactory(getApplication()) - .create(UploadHistoryViewModel.class); - - // Observe the history data - viewModel.getAllHistory().observe(this, new Observer>() { - @Override - public void onChanged(List history) { - // Update the cached copy of the history in the adapter - adapter.setHistory(history); - - // Show/hide empty view - if (history == null || history.isEmpty()) { - recyclerView.setVisibility(View.GONE); - emptyView.setVisibility(View.VISIBLE); - } else { - recyclerView.setVisibility(View.VISIBLE); - emptyView.setVisibility(View.GONE); - } - } - }); - - // Observe the delete status to provide feedback - viewModel.getDeleteStatus().observe(this, deleteSuccess -> { - if (deleteSuccess != null) { - if (deleteSuccess) { - Toast.makeText(this, "File deleted from server successfully", Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(this, "Failed to delete file from server, but removed from local history", - Toast.LENGTH_LONG).show(); - } - } - }); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - - if (id == android.R.id.home) { - finish(); - return true; - } else if (id == R.id.action_clear_history) { - // Show confirmation dialog - new AlertDialog.Builder(this) - .setTitle("Clear All History") - .setMessage("Do you want to delete all uploads from both local history and the server?") - .setPositiveButton("Yes", (dialog, which) -> { - // Show progress dialog - AlertDialog progressDialog = new AlertDialog.Builder(this) - .setTitle("Deleting...") - .setMessage("Attempting to delete all files from server and clear history.") - .setCancelable(false) - .create(); - progressDialog.show(); - - // Attempt to delete all - viewModel.deleteAll() - .thenAccept(unused -> { - runOnUiThread(() -> { - progressDialog.dismiss(); - // Toast message will be shown through the observer - }); - }); - }) - .setNegativeButton("No", null) - .show(); - return true; - } - - return super.onOptionsItemSelected(item); - } - - @Override - public void onCopyCodeClick(UploadHistory history) { - copyToClipboard(history.getUploadCode()); - } - - @Override - public void onDeleteClick(UploadHistory history) { - // Show confirmation dialog - new AlertDialog.Builder(this) - .setTitle("Delete Upload") - .setMessage("Do you want to delete this upload from both local history and the server?") - .setPositiveButton("Yes", (dialog, which) -> { - // Show progress dialog - AlertDialog progressDialog = new AlertDialog.Builder(this) - .setTitle("Deleting...") - .setMessage("Attempting to delete from server and local history.") - .setCancelable(false) - .create(); - progressDialog.show(); - - // Attempt to delete - viewModel.delete(history) - .thenAccept(unused -> { - runOnUiThread(() -> { - progressDialog.dismiss(); - }); - }); - }) - .setNegativeButton("No", null) - .show(); - } - - /** - * Copy text to clipboard - * - * @param text The text to copy - */ - private void copyToClipboard(String text) { - try { - ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Upload Code", text); - clipboard.setPrimaryClip(clip); - Toast.makeText(this, "Code copied to clipboard", Toast.LENGTH_SHORT).show(); - } catch (Exception e) { - Log.e(TAG, "Error copying to clipboard", e); - Toast.makeText(this, "Failed to copy code", Toast.LENGTH_SHORT).show(); - } - } -} diff --git a/app/src/main/java/com/mattintech/simplelogupload/view/UploadParamsDialog.java b/app/src/main/java/com/mattintech/simplelogupload/view/UploadParamsDialog.java deleted file mode 100644 index ee5609a..0000000 --- a/app/src/main/java/com/mattintech/simplelogupload/view/UploadParamsDialog.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.mattintech.simplelogupload.view; - -import android.app.Dialog; -import android.content.Context; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.View; -import android.view.Window; -import android.widget.Button; -import android.widget.EditText; - -import androidx.annotation.NonNull; - -import com.mattintech.simplelogupload.R; -import com.mattintech.simplelogupload.constant.NetworkConstants; - -/** - * Dialog for setting upload parameters - */ -public class UploadParamsDialog extends Dialog { - - private EditText maxDownloadsInput; - private EditText expiryTimeInput; - private final UploadParamsListener listener; - - /** - * Interface for upload parameter callbacks - */ - public interface UploadParamsListener { - void onUploadParamsSet(int maxDownloads, int expiryHours); - void onUploadParamsCancelled(); - } - - /** - * Constructor - * - * @param context The context - * @param listener Callback listener - */ - public UploadParamsDialog(@NonNull Context context, UploadParamsListener listener) { - super(context); - this.listener = listener; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_NO_TITLE); - setContentView(R.layout.dialog_upload_params); - - // Initialize views - maxDownloadsInput = findViewById(R.id.maxDownloadsInput); - expiryTimeInput = findViewById(R.id.expiryTimeInput); - - // Set default values as hints - maxDownloadsInput.setHint("Default: " + NetworkConstants.DEFAULT_MAX_DOWNLOADS); - expiryTimeInput.setHint("Default: " + NetworkConstants.DEFAULT_EXPIRY_HOURS); - - // Set up buttons - Button confirmButton = findViewById(R.id.confirmButton); - Button cancelButton = findViewById(R.id.cancelButton); - - confirmButton.setOnClickListener(v -> { - int maxDownloads = getMaxDownloadsValue(); - int expiryHours = getExpiryHoursValue(); - - // Validate expiry time - if (expiryHours > NetworkConstants.MAX_EXPIRY_HOURS) { - expiryTimeInput.setError("Maximum expiry time is " + - NetworkConstants.MAX_EXPIRY_HOURS + " hours"); - return; - } - - // Validate max downloads (must be non-negative) - if (maxDownloads < 0) { - maxDownloadsInput.setError("Downloads must be 0 or greater"); - return; - } - - if (listener != null) { - listener.onUploadParamsSet(maxDownloads, expiryHours); - } - dismiss(); - }); - - cancelButton.setOnClickListener(v -> { - if (listener != null) { - listener.onUploadParamsCancelled(); - } - dismiss(); - }); - } - - /** - * Get the max downloads value from the input - * - * @return The max downloads value (default if not set) - */ - private int getMaxDownloadsValue() { - if (TextUtils.isEmpty(maxDownloadsInput.getText())) { - return NetworkConstants.DEFAULT_MAX_DOWNLOADS; - } - - try { - return Integer.parseInt(maxDownloadsInput.getText().toString()); - } catch (NumberFormatException e) { - return NetworkConstants.DEFAULT_MAX_DOWNLOADS; - } - } - - /** - * Get the expiry hours value from the input - * - * @return The expiry hours value (default if not set) - */ - private int getExpiryHoursValue() { - if (TextUtils.isEmpty(expiryTimeInput.getText())) { - return NetworkConstants.DEFAULT_EXPIRY_HOURS; - } - - try { - return Integer.parseInt(expiryTimeInput.getText().toString()); - } catch (NumberFormatException e) { - return NetworkConstants.DEFAULT_EXPIRY_HOURS; - } - } -} diff --git a/app/src/main/java/com/mattintech/simplelogupload/view/UploadResultDialog.java b/app/src/main/java/com/mattintech/simplelogupload/view/UploadResultDialog.java deleted file mode 100644 index 8962735..0000000 --- a/app/src/main/java/com/mattintech/simplelogupload/view/UploadResultDialog.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.mattintech.simplelogupload.view; - -import android.app.Dialog; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.os.Bundle; -import android.util.Log; -import android.view.View; -import android.view.Window; -import android.widget.Button; -import android.widget.TextView; -import android.widget.Toast; - -import com.mattintech.simplelogupload.R; -import com.mattintech.simplelogupload.constant.AppConstants; - -/** - * Dialog to display upload result - */ -public class UploadResultDialog extends Dialog { - private static final String TAG = AppConstants.APP_TAG + "ResultDialog"; - - private final String uploadCode; - - /** - * Constructor - * - * @param context The context - * @param uploadCode The upload code - */ - public UploadResultDialog(Context context, String uploadCode) { - super(context); - this.uploadCode = uploadCode; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_NO_TITLE); - setContentView(R.layout.upload_result_dialog); - - TextView codeTextView = findViewById(R.id.uploadCode); - codeTextView.setText(uploadCode); - - Button copyButton = findViewById(R.id.copyButton); - copyButton.setOnClickListener(v -> copyCodeToClipboard()); - - Button closeButton = findViewById(R.id.closeButton); - closeButton.setOnClickListener(v -> dismiss()); - } - - /** - * Copy the upload code to the clipboard - */ - private void copyCodeToClipboard() { - try { - ClipboardManager clipboard = (ClipboardManager) - getContext().getSystemService(Context.CLIPBOARD_SERVICE); - - ClipData clip = ClipData.newPlainText("Upload Code", uploadCode); - clipboard.setPrimaryClip(clip); - - Toast.makeText(getContext(), "Code copied to clipboard", Toast.LENGTH_SHORT).show(); - } catch (Exception e) { - Log.e(TAG, "Error copying to clipboard", e); - Toast.makeText(getContext(), "Failed to copy code", Toast.LENGTH_SHORT).show(); - } - } -} diff --git a/app/src/main/java/com/mattintech/simplelogupload/view/WelcomeActivity.java b/app/src/main/java/com/mattintech/simplelogupload/view/WelcomeActivity.java deleted file mode 100644 index c883fda..0000000 --- a/app/src/main/java/com/mattintech/simplelogupload/view/WelcomeActivity.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.mattintech.simplelogupload.view; - -import android.content.Intent; -import android.os.Bundle; -import android.widget.Button; - -import androidx.appcompat.app.AppCompatActivity; - -import com.mattintech.simplelogupload.R; - -/** - * Welcome activity for first-time setup - */ -public class WelcomeActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_welcome); - - // Hide action bar for cleaner look - if (getSupportActionBar() != null) { - getSupportActionBar().hide(); - } - - // Set up next button - Button nextButton = findViewById(R.id.nextButton); - nextButton.setOnClickListener(v -> { - // Proceed to server setup activity - Intent intent = new Intent(WelcomeActivity.this, ServerSetupActivity.class); - startActivity(intent); - // No finish() here to allow back navigation - }); - } -} diff --git a/app/src/main/java/com/mattintech/simplelogupload/view/adapter/UploadHistoryAdapter.java b/app/src/main/java/com/mattintech/simplelogupload/view/adapter/UploadHistoryAdapter.java deleted file mode 100644 index ba191d7..0000000 --- a/app/src/main/java/com/mattintech/simplelogupload/view/adapter/UploadHistoryAdapter.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.mattintech.simplelogupload.view.adapter; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.mattintech.simplelogupload.R; -import com.mattintech.simplelogupload.model.UploadHistory; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; - -/** - * Adapter for displaying upload history items - */ -public class UploadHistoryAdapter extends RecyclerView.Adapter { - - private List history = new ArrayList<>(); - private final OnHistoryItemClickListener listener; - private final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd, yyyy HH:mm", Locale.getDefault()); - - /** - * Interface for history item click events - */ - public interface OnHistoryItemClickListener { - void onCopyCodeClick(UploadHistory history); - void onDeleteClick(UploadHistory history); - } - - /** - * Constructor - * - * @param listener The click listener - */ - public UploadHistoryAdapter(OnHistoryItemClickListener listener) { - this.listener = listener; - } - - /** - * Update the history data - * - * @param history The new history data - */ - public void setHistory(List history) { - this.history = history; - notifyDataSetChanged(); - } - - @NonNull - @Override - public HistoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View itemView = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_upload_history, parent, false); - return new HistoryViewHolder(itemView); - } - - @Override - public void onBindViewHolder(@NonNull HistoryViewHolder holder, int position) { - UploadHistory current = history.get(position); - - holder.fileNameTextView.setText(current.getFileName()); - holder.uploadCodeTextView.setText(current.getUploadCode()); - - // Format date - String formattedDate = dateFormat.format(new Date(current.getUploadTime())); - holder.uploadTimeTextView.setText(formattedDate); - - // Set details text - String details = String.format(Locale.getDefault(), - "Max downloads: %d • Expires in %d hours", - current.getMaxDownloads(), - current.getExpiryHours()); - holder.detailsTextView.setText(details); - - // Set click listeners - holder.copyButton.setOnClickListener(v -> { - if (listener != null) { - listener.onCopyCodeClick(current); - } - }); - - holder.deleteButton.setOnClickListener(v -> { - if (listener != null) { - listener.onDeleteClick(current); - } - }); - } - - @Override - public int getItemCount() { - return history.size(); - } - - /** - * ViewHolder for history items - */ - static class HistoryViewHolder extends RecyclerView.ViewHolder { - private final TextView fileNameTextView; - private final TextView uploadCodeTextView; - private final TextView uploadTimeTextView; - private final TextView detailsTextView; - private final Button copyButton; - private final Button deleteButton; - - HistoryViewHolder(View itemView) { - super(itemView); - fileNameTextView = itemView.findViewById(R.id.fileNameText); - uploadCodeTextView = itemView.findViewById(R.id.uploadCodeText); - uploadTimeTextView = itemView.findViewById(R.id.uploadTimeText); - detailsTextView = itemView.findViewById(R.id.detailsText); - copyButton = itemView.findViewById(R.id.copyButton); - deleteButton = itemView.findViewById(R.id.deleteButton); - } - } -} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index c3e43da..0000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - - - - - - -