Remove legacy XML UI code after Compose migration

This commit is contained in:
2025-12-27 18:55:54 -05:00
parent 2c8315cb5d
commit 4a7a48c9a4
23 changed files with 2 additions and 3102 deletions

View File

@@ -49,52 +49,6 @@
</intent-filter>
</activity>
<!-- OLD: Legacy MainActivity (DEPRECATED - kept for reference only) -->
<activity
android:name=".view.MainActivity"
android:exported="false">
<!-- Removed LAUNCHER intent - now using ComposeMainActivity -->
</activity>
<!-- Upload History Activity -->
<activity
android:name=".view.UploadHistoryActivity"
android:label="Upload History"
android:parentActivityName=".view.MainActivity"
android:exported="false">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".view.MainActivity" />
</activity>
<!-- Settings Activity -->
<activity
android:name=".view.SettingsActivity"
android:label="Settings"
android:parentActivityName=".view.MainActivity"
android:exported="false">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".view.MainActivity" />
</activity>
<!-- Welcome Activity -->
<activity
android:name=".view.WelcomeActivity"
android:label="Welcome"
android:exported="false"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<!-- Server Setup Activity -->
<activity
android:name=".view.ServerSetupActivity"
android:label="Server Setup"
android:exported="false">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".view.WelcomeActivity" />
</activity>
<!-- File Upload Foreground Service -->
<service
android:name=".service.FileUploadForegroundService"

View File

@@ -1,212 +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.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Adapter for displaying dumpstate files in a RecyclerView with multi-selection capability
*/
public class DumpstateAdapter extends RecyclerView.Adapter<DumpstateAdapter.ViewHolder> {
private final List<File> files;
private final Context context;
private final Set<Integer> selectedPositions = new HashSet<>();
private OnSelectionChangedListener selectionChangedListener;
/**
* Constructor
*
* @param context The context
* @param files List of dumpstate files
*/
public DumpstateAdapter(Context context, List<File> 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<File> getSelectedFiles() {
List<File> 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);
}
}

View File

@@ -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<DumpstateFileAdapter.ViewHolder> {
private final List<File> 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<File> 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);
}
}

View File

@@ -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);

View File

@@ -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<File> 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);
});
}
}

View File

@@ -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<String> 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<File> 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<File> 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<Uri> 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);
}
}

View File

@@ -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<ScanOptions> 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;
}
}

View File

@@ -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<ScanOptions> 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;
}
}

View File

@@ -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<List<UploadHistory>>() {
@Override
public void onChanged(List<UploadHistory> 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();
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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
});
}
}

View File

@@ -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<UploadHistoryAdapter.HistoryViewHolder> {
private List<UploadHistory> 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<UploadHistory> 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);
}
}
}

View File

@@ -1,200 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/headerText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_header"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"/>
<!-- Main Upload Options Section -->
<androidx.cardview.widget.CardView
android:id="@+id/uploadOptionsCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
app:layout_constraintTop_toBottomOf="@id/headerText">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/upload_options_title"
android:textSize="18sp"
android:textStyle="bold"/>
<Button
android:id="@+id/selectFileButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/select_file_button"
android:drawableStart="@android:drawable/ic_menu_search"
android:paddingStart="16dp"
android:paddingEnd="16dp"/>
<Button
android:id="@+id/uploadDumpstateButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/upload_dumpstate_button"
android:drawableStart="@android:drawable/ic_menu_save"
android:paddingStart="16dp"
android:paddingEnd="16dp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Dumpstate Section -->
<androidx.cardview.widget.CardView
android:id="@+id/dumpstateCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
app:layout_constraintTop_toBottomOf="@id/uploadOptionsCard">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dumpstate_section_title"
android:textSize="18sp"
android:textStyle="bold"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="4dp">
<TextView
android:id="@+id/dumpstateStatusText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/looking_for_dumpstate"
android:textSize="16sp"
android:maxLines="3"
android:ellipsize="start"/>
<Button
android:id="@+id/toggleDumpstateListButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/show_dumpstates"
android:textSize="12sp"
style="@style/Widget.AppCompat.Button.Small" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dumpstateRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:visibility="gone"/>
<LinearLayout
android:id="@+id/multiSelectButtonsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp"
android:visibility="gone">
<Button
android:id="@+id/selectAllButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/select_all"
android:textSize="12sp"
style="@style/Widget.AppCompat.Button.Borderless.Colored" />
<Button
android:id="@+id/clearSelectionsButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/clear_selections"
android:textSize="12sp"
style="@style/Widget.AppCompat.Button.Borderless.Colored" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Selected File Section -->
<androidx.cardview.widget.CardView
android:id="@+id/selectedFileCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/dumpstateCard">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/selected_file_section_title"
android:textSize="18sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/selectedFileText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/no_file_selected"
android:textSize="16sp"/>
<Button
android:id="@+id/uploadSelectedFileButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/upload_selected_file"
android:visibility="gone"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/selectedFileCard"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,134 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp">
<TextView
android:id="@+id/setupTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Server Configuration"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/setupDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Please enter your server details below. These are required to connect to your SimpleLogUpload server."
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/setupTitle" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/finishSetupButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/setupDescription">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/serverAddressLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="Server Address (IP or hostname)">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/serverAddressInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/serverPortLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="Server Port">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/serverPortInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/clientKeyLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="Client Key">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/clientKeyInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<!-- QR Code Scan Button -->
<Button
android:id="@+id/scanQrButton"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/scan_qr_code_button"
android:drawableLeft="@drawable/ic_qr_scan"
android:drawablePadding="8dp"
android:paddingVertical="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/scan_qr_code_hint"
android:textAlignment="center"
android:textSize="12sp"
android:textStyle="italic" />
<TextView
android:id="@+id/testConnectionInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="You can test your connection after saving these settings."
android:textAlignment="center"
android:textStyle="italic" />
</LinearLayout>
</ScrollView>
<Button
android:id="@+id/finishSetupButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Save and Continue"
android:padding="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,98 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Server Configuration"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginBottom="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:hint="Server Address">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/serverAddressInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:hint="Server Port">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/serverPortInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Authentication"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:hint="Client Key"
app:endIconMode="password_toggle">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/clientKeyInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<!-- QR Code Scan Button -->
<Button
android:id="@+id/scanQrButton"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:text="@string/scan_qr_code_button"
android:drawableLeft="@drawable/ic_qr_scan"
android:drawablePadding="8dp" />
<Button
android:id="@+id/saveSettingsButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Save Settings"
android:layout_marginTop="16dp" />
<Button
android:id="@+id/resetSettingsButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Reset to Defaults"
android:layout_marginTop="8dp"
style="@style/Widget.AppCompat.Button.Borderless.Colored" />
</LinearLayout>
</ScrollView>

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.UploadHistoryActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/historyRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:padding="8dp"
android:clipToPadding="false" />
<TextView
android:id="@+id/emptyView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="No upload history"
android:textSize="18sp"
android:visibility="gone" />
</RelativeLayout>

View File

@@ -1,65 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp">
<ImageView
android:id="@+id/logoImage"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_marginTop="32dp"
android:src="@drawable/ic_app_icon"
android:contentDescription="SimpleLogUpload logo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/welcomeTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Welcome to SimpleLogUpload"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/logoImage" />
<TextView
android:id="@+id/welcomeDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Before you can use this app, you need to configure it to connect to your SimpleLogUpload server instance."
android:textAlignment="center"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/welcomeTitle" />
<TextView
android:id="@+id/welcomeDetails"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="The app requires:\n\n• Server Address - IP or hostname of your server\n• Server Port - Port your server is running on\n• Client Key - Authentication key for secure uploads\n\nYour administrator should have provided these details."
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/welcomeDescription" />
<Button
android:id="@+id/nextButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Continue to Setup"
android:padding="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,53 +0,0 @@
<?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="vertical"
android:padding="16dp">
<TextView
android:id="@+id/titleText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dumpstate_selection_title"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="16dp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dumpstateRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="300dp"/>
<TextView
android:id="@+id/emptyView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/no_dumpstate_found"
android:gravity="center"
android:visibility="gone"
android:padding="16dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="end"
android:layout_marginTop="16dp">
<Button
android:id="@+id/cancelButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@android:string/cancel"
style="@style/Widget.AppCompat.Button.Borderless"/>
<Button
android:id="@+id/selectButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/select_button"
android:enabled="false"/>
</LinearLayout>
</LinearLayout>

View File

@@ -1,97 +0,0 @@
<?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="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/upload_params_title"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="16dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/max_downloads_label"
android:textSize="16sp" />
<EditText
android:id="@+id/maxDownloadsInput"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="number"
android:hint="@string/max_downloads_hint"
android:importantForAutofill="no" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/max_downloads_note"
android:textSize="12sp"
android:textStyle="italic"
android:layout_marginBottom="16dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/expiry_time_label"
android:textSize="16sp" />
<EditText
android:id="@+id/expiryTimeInput"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="number"
android:hint="@string/expiry_time_hint"
android:importantForAutofill="no" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/expiry_time_note"
android:textSize="12sp"
android:textStyle="italic"
android:layout_marginBottom="16dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="end">
<Button
android:id="@+id/cancelButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cancel_button"
style="@style/Widget.AppCompat.Button.Borderless" />
<Button
android:id="@+id/confirmButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/upload_button" />
</LinearLayout>
</LinearLayout>

View File

@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:background="?attr/selectableItemBackground">
<CheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:id="@+id/fileNameText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="15sp"
android:textStyle="bold"
android:ellipsize="start"
android:singleLine="true"
android:layout_marginStart="8dp"
app:layout_constraintStart_toEndOf="@id/checkBox"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/fileDateText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="14sp"
android:layout_marginStart="8dp"
app:layout_constraintStart_toEndOf="@id/checkBox"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/fileNameText"/>
<TextView
android:id="@+id/fileSizeText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="14sp"
android:layout_marginStart="8dp"
app:layout_constraintStart_toEndOf="@id/checkBox"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/fileDateText"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,90 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/fileNameText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="16sp"
android:maxLines="1"
android:ellipsize="end" />
<TextView
android:id="@+id/uploadTimeText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#757575"
android:layout_marginTop="4dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Code: "
android:textStyle="bold" />
<TextView
android:id="@+id/uploadCodeText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textIsSelectable="true"
android:textStyle="bold"
android:textColor="#0066CC" />
</LinearLayout>
<TextView
android:id="@+id/detailsText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="14sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp">
<Button
android:id="@+id/copyButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Copy Code"
style="@style/Widget.AppCompat.Button.Colored" />
<Button
android:id="@+id/deleteButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:text="Delete"
style="@style/Widget.AppCompat.Button.Borderless.Colored" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -1,70 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:id="@+id/dialogTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Upload Successful"
android:textAlignment="center"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/codeLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Your upload code:"
android:textAlignment="center"
app:layout_constraintTop_toBottomOf="@id/dialogTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/uploadCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAlignment="center"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="#007BFF"
app:layout_constraintTop_toBottomOf="@id/codeLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/copyButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="4dp"
android:text="Copy Code"
app:layout_constraintTop_toBottomOf="@id/uploadCode"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/closeButton"
app:layout_constraintHorizontal_weight="1" />
<Button
android:id="@+id/closeButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginStart="4dp"
android:text="Close"
style="@style/Widget.AppCompat.Button.Borderless.Colored"
app:layout_constraintTop_toBottomOf="@id/uploadCode"
app:layout_constraintStart_toEndOf="@id/copyButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1" />
</androidx.constraintlayout.widget.ConstraintLayout>