From: Soren Stoutner Date: Wed, 12 Feb 2020 18:37:42 +0000 (-0700) Subject: Add an option to save a raw URL. https://redmine.stoutner.com/issues/463 X-Git-Tag: v3.4~8 X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=commitdiff_plain;h=adbf486b6abcc9387ff89f87c97503a8c58aedb2 Add an option to save a raw URL. https://redmine.stoutner.com/issues/463 --- diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index 4cef6813..166dc35b 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -119,6 +119,7 @@ import com.stoutner.privacybrowser.R; import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter; import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses; import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists; +import com.stoutner.privacybrowser.asynctasks.SaveUrl; import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage; import com.stoutner.privacybrowser.dialogs.AdConsentDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog; @@ -213,6 +214,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private final int PERMISSION_OPEN_REQUEST_CODE = 2; private final int PERMISSION_SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE = 3; private final int PERMISSION_SAVE_WEBPAGE_IMAGE_REQUEST_CODE = 4; + private final int PERMISSION_SAVE_WEBPAGE_RAW_REQUEST_CODE = 5; // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`. @@ -324,6 +326,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // The file path strings are used in `onSaveWebpageImage()` and `onRequestPermissionResult()` private String openFilePath; + private String saveWebpageUrl; private String saveWebpageFilePath; @Override @@ -880,79 +883,79 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants. if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser. // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_privacy_browser)); + userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_privacy_browser)); // Select the Privacy Browser radio box. menu.findItem(R.id.user_agent_privacy_browser).setChecked(true); } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default. // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_webview_default)); + userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_webview_default)); // Select the WebView Default radio box. menu.findItem(R.id.user_agent_webview_default).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android. // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_firefox_on_android)); + userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_android)); // Select the Firefox on Android radio box. menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android. // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_chrome_on_android)); + userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_android)); // Select the Chrome on Android radio box. menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS. // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_safari_on_ios)); + userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_ios)); // Select the Safari on iOS radio box. menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux. // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux)); + userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux)); // Select the Firefox on Linux radio box. menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux. // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux)); + userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux)); // Select the Chromium on Linux radio box. menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows. // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows)); + userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows)); // Select the Firefox on Windows radio box. menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows. // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows)); + userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows)); // Select the Chrome on Windows radio box. menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows. // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_edge_on_windows)); + userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_edge_on_windows)); // Select the Edge on Windows radio box. menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows. // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows)); + userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows)); // Select the Internet on Windows radio box. menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS. // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_safari_on_macos)); + userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_macos)); // Select the Safari on macOS radio box. menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true); } else { // Custom user agent. // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_custom)); + userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_custom)); // Select the Custom radio box. menu.findItem(R.id.user_agent_custom).setChecked(true); @@ -1704,22 +1707,32 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Consume the event. return true; + case R.id.save_url: + // Instantiate the save dialog. + DialogFragment saveDialogFragment = SaveWebpageDialog.saveUrl(StoragePermissionDialog.SAVE, currentWebView.getCurrentUrl()); + + // Show the save dialog. It must be named `save_dialog` so that the file picked can update the file name. + saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); + + // Consume the event. + return true; + case R.id.save_as_archive: // Instantiate the save webpage archive dialog. - DialogFragment saveWebpageArchiveDialogFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_ARCHIVE); + DialogFragment saveWebpageArchiveDialogFragment = SaveWebpageDialog.saveUrl(StoragePermissionDialog.SAVE_AS_ARCHIVE, currentWebView.getCurrentUrl()); - // Show the save webpage archive dialog. - saveWebpageArchiveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_webpage)); + // Show the save webpage archive dialog. It must be named `save_dialog` so that the file picked can update the file name. + saveWebpageArchiveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); // Consume the event. return true; case R.id.save_as_image: - // Instantiate the save webpage image dialog. - DialogFragment saveWebpageImageDialogFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_IMAGE); + // Instantiate the save webpage image dialog. It must be named `save_webpage` so that the file picked can update the file name. + DialogFragment saveWebpageImageDialogFragment = SaveWebpageDialog.saveUrl(StoragePermissionDialog.SAVE_AS_IMAGE, currentWebView.getCurrentUrl()); - // Show the save webpage image dialog. - saveWebpageImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_webpage)); + // Show the save webpage image dialog. It must be named `save_dialog` so that the file picked can update the file name. + saveWebpageImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); // Consume the event. return true; @@ -2047,8 +2060,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get handles for the system managers. final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - FragmentManager fragmentManager = getSupportFragmentManager(); - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Remove the lint errors below that the clipboard manager might be null. assert clipboardManager != null; @@ -2109,38 +2120,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; }); - // Add a Download URL entry. - menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> { - // Check if the download should be processed by an external app. - if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app. - openUrlWithExternalApp(linkUrl); - } else { // Download with Android's download manager. - // Check to see if the storage permission has already been granted. - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested. - // Store the variables for future use by `onRequestPermissionsResult()`. - downloadUrl = linkUrl; - downloadContentDisposition = "none"; - downloadContentLength = -1; - - // Show a dialog if the user has previously denied the permission. - if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE. - DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE); - - // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed. - downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location)); - } else { // Show the permission request directly. - // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`. - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_FILE_REQUEST_CODE); - } - } else { // The storage permission has already been granted. - // Get a handle for the download file alert dialog. - DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1); + // Add a Save URL entry. + menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> { + // Instantiate the save dialog. + DialogFragment saveDialogFragment = SaveWebpageDialog.saveUrl(StoragePermissionDialog.SAVE, linkUrl); - // Show the download file alert dialog. - downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)); - } - } + // Show the save dialog. It must be named `save_dialog` so that the file picked can update the file name. + saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); // Consume the event. return true; @@ -2217,36 +2203,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; }); - // Add a Download Image entry. - menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> { - // Check if the download should be processed by an external app. - if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app. - openUrlWithExternalApp(imageUrl); - } else { // Download with Android's download manager. - // Check to see if the storage permission has already been granted. - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested. - // Store the image URL for use by `onRequestPermissionResult()`. - downloadImageUrl = imageUrl; - - // Show a dialog if the user has previously denied the permission. - if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE. - DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE); - - // Show the download location permission alert dialog. The permission will be requested when the dialog is closed. - downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location)); - } else { // Show the permission request directly. - // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`. - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_IMAGE_REQUEST_CODE); - } - } else { // The storage permission has already been granted. - // Get a handle for the download image alert dialog. - DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); + // Add a Save Image entry. + menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> { + // Instantiate the save dialog. + DialogFragment saveDialogFragment = SaveWebpageDialog.saveUrl(StoragePermissionDialog.SAVE, imageUrl); - // Show the download image alert dialog. - downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)); - } - } + // Show the save dialog. It must be named `save_dialog` so that the file picked can update the file name. + saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); // Consume the event. return true; @@ -2342,41 +2305,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; }); - // Add a Download Image entry. - menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> { - // Check if the download should be processed by an external app. - if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app. - openUrlWithExternalApp(imageUrl); - } else { // Download with Android's download manager. - // Check to see if the storage permission has already been granted. - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested. - // Store the image URL for use by `onRequestPermissionResult()`. - downloadImageUrl = imageUrl; - - // Show a dialog if the user has previously denied the permission. - if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE. - DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE); - - // Show the download location permission alert dialog. The permission will be requested when the dialog is closed. - downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location)); - } else { // Show the permission request directly. - // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`. - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_IMAGE_REQUEST_CODE); - } - } else { // The storage permission has already been granted. - // Get a handle for the download image alert dialog. - DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); - - // Show the download image alert dialog. - downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)); - } - } - - // Consume the event. - return true; - }); - // Add a Copy URL entry. menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> { // Save the link URL in a clip data. @@ -2389,6 +2317,28 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; }); + menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> { + // Instantiate the save dialog. + DialogFragment saveDialogFragment = SaveWebpageDialog.saveUrl(StoragePermissionDialog.SAVE, imageUrl); + + // Show the save raw dialog. It must be named `save_dialog` so that the file picked can update the file name. + saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); + + // Consume the event. + return true; + }); + + menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> { + // Instantiate the save dialog. + DialogFragment saveDialogFragment = SaveWebpageDialog.saveUrl(StoragePermissionDialog.SAVE, linkUrl); + + // Show the save raw dialog. It must be named `save_dialog` so that the file picked can update the file name. + saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); + + // Consume the event. + return true; + }); + // Add an Open with App entry. menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> { // Open the link URL with an external app. @@ -2928,7 +2878,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Don't do anything if the user pressed back from the file picker. if (resultCode == Activity.RESULT_OK) { // Get a handle for the save dialog fragment. - DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_webpage)); + DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog)); // Only update the file name if the dialog still exists. if (saveWebpageDialogFragment != null) { @@ -3202,22 +3152,29 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Remove the incorrect lint warning below that the dialog might be null. assert dialog != null; - // Get a handle for the file name edit text. + // Get a handle for the edit texts. + EditText urlEditText = dialog.findViewById(R.id.url_edittext); EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext); - // Get the file path string. + // Get the strings from the edit texts. + saveWebpageUrl = urlEditText.getText().toString(); saveWebpageFilePath = fileNameEditText.getText().toString(); // Check to see if the storage permission is needed. if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted. //Save the webpage according to the save type. switch (saveType) { - case StoragePermissionDialog.SAVE_ARCHIVE: + case StoragePermissionDialog.SAVE: + // Save the URL. + new SaveUrl(this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); + break; + + case StoragePermissionDialog.SAVE_AS_ARCHIVE: // Save the webpage archive. currentWebView.saveWebArchive(saveWebpageFilePath); break; - case StoragePermissionDialog.SAVE_IMAGE: + case StoragePermissionDialog.SAVE_AS_IMAGE: // Save the webpage image. new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath); break; @@ -3236,12 +3193,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (saveWebpageFilePath.startsWith(externalPrivateDirectory)) { // The file path is in the external private directory. // Save the webpage according to the save type. switch (saveType) { - case StoragePermissionDialog.SAVE_ARCHIVE: + case StoragePermissionDialog.SAVE: + // Save the URL. + new SaveUrl(this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); + break; + + case StoragePermissionDialog.SAVE_AS_ARCHIVE: // Save the webpage archive. currentWebView.saveWebArchive(saveWebpageFilePath); break; - case StoragePermissionDialog.SAVE_IMAGE: + case StoragePermissionDialog.SAVE_AS_IMAGE: // Save the webpage image. new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath); break; @@ -3256,12 +3218,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission)); } else { // Show the permission request directly. switch (saveType) { - case StoragePermissionDialog.SAVE_ARCHIVE: + case StoragePermissionDialog.SAVE: + // Request the write external storage permission. The URL will be saved when it finishes. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_WEBPAGE_RAW_REQUEST_CODE); + + case StoragePermissionDialog.SAVE_AS_ARCHIVE: // Request the write external storage permission. The webpage archive will be saved when it finishes. ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE); break; - case StoragePermissionDialog.SAVE_IMAGE: + case StoragePermissionDialog.SAVE_AS_IMAGE: // Request the write external storage permission. The webpage image will be saved when it finishes. ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_WEBPAGE_IMAGE_REQUEST_CODE); break; @@ -3279,12 +3245,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_OPEN_REQUEST_CODE); break; - case StoragePermissionDialog.SAVE_ARCHIVE: + case StoragePermissionDialog.SAVE: + // Request the write external storage permission. The URL will be saved when it finishes. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_WEBPAGE_RAW_REQUEST_CODE); + break; + + case StoragePermissionDialog.SAVE_AS_ARCHIVE: // Request the write external storage permission. The webpage archive will be saved when it finishes. ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE); break; - case StoragePermissionDialog.SAVE_IMAGE: + case StoragePermissionDialog.SAVE_AS_IMAGE: // Request the write external storage permission. The webpage image will be saved when it finishes. ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_WEBPAGE_IMAGE_REQUEST_CODE); break; @@ -3370,6 +3341,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reset the save webpage file path. saveWebpageFilePath = ""; break; + + case PERMISSION_SAVE_WEBPAGE_RAW_REQUEST_CODE: + // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. + if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted. + // Save the raw URL. + new SaveUrl(this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); + } else { // The storage permission was not granted. + // Display an error snackbar. + Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); + } + + // Reset the save strings. + saveWebpageUrl = ""; + saveWebpageFilePath = ""; + break; } } @@ -4687,20 +4673,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - private void openUrlWithExternalApp(String url) { - // Create a download intent. Not specifying the action type will display the maximum number of options. - Intent downloadIntent = new Intent(); - - // Set the URI and the MIME type. Specifying `text/html` displays a good number of options. - downloadIntent.setDataAndType(Uri.parse(url), "text/html"); - - // Flag the intent to open in a new task. - downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - // Show the chooser. - startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with))); - } - private void highlightUrlText() { // Get a handle for the URL edit text. EditText urlEditText = findViewById(R.id.url_edittext); diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java index 30f9aee9..c9a9ea33 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java +++ b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 Soren Stoutner . + * Copyright © 2017-2020 Soren Stoutner . * * This file is part of Privacy Browser . * @@ -365,10 +365,10 @@ public class GetSource extends AsyncTask // Only process the cookies if they are not null. if (cookiesString != null) { - // Set the `Cookie` header property. + // Add the cookies to the header property. httpUrlConnection.setRequestProperty("Cookie", cookiesString); - // Add the `Cookie` header to the string builder and format the text. + // Add the cookie header to the string builder and format the text. requestHeadersBuilder.append(System.getProperty("line.separator")); if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); @@ -457,17 +457,17 @@ public class GetSource extends AsyncTask ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] conversionBufferByteArray = new byte[1024]; - // Instantiate the variable to track the buffer length. + // Define the buffer length variable. int bufferLength; try { - // Attempt to read data from the input stream and store it in the conversion buffer byte array. Also store the amount of data transferred in the buffer length variable. + // Attempt to read data from the input stream and store it in the conversion buffer byte array. Also store the amount of data read in the buffer length variable. while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) { // Proceed while the amount of data stored in the buffer is > 0. // Write the contents of the conversion buffer to the byte array output stream. byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength); } - } catch (IOException e) { - e.printStackTrace(); + } catch (IOException exception) { + // Do nothing. } // Close the input stream. @@ -476,11 +476,11 @@ public class GetSource extends AsyncTask // Populate the response body string with the contents of the byte array output stream. responseBodyBuilder.append(byteArrayOutputStream.toString()); } finally { - // Disconnect `httpUrlConnection`. + // Disconnect HTTP URL connection. httpUrlConnection.disconnect(); } - } catch (IOException e) { - e.printStackTrace(); + } catch (IOException exception) { + exception.printStackTrace(); } // Return the response body string as the result. diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveUrl.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveUrl.java new file mode 100644 index 00000000..fd72db47 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveUrl.java @@ -0,0 +1,199 @@ +/* + * Copyright © 2020 Soren Stoutner . + * + * This file is part of Privacy Browser . + * + * Privacy Browser is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser. If not, see . + */ + +package com.stoutner.privacybrowser.asynctasks; + +import android.app.Activity; +import android.os.AsyncTask; +import android.webkit.CookieManager; + +import com.google.android.material.snackbar.Snackbar; +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.views.NoSwipeViewPager; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.ref.WeakReference; +import java.net.HttpURLConnection; +import java.net.URL; + +public class SaveUrl extends AsyncTask { + // Define a weak reference to the calling activity. + private WeakReference activityWeakReference; + + // Define a success string constant. + private final String SUCCESS = "Success"; + + // Define the class variables. + private String filePathString; + private String userAgent; + private boolean cookiesEnabled; + private Snackbar savingFileSnackbar; + + // The public constructor. + public SaveUrl(Activity activity, String filePathString, String userAgent, boolean cookiesEnabled) { + // Populate the weak reference to the calling activity. + activityWeakReference = new WeakReference<>(activity); + + // Store the class variables. + this.filePathString = filePathString; + this.userAgent = userAgent; + this.cookiesEnabled = cookiesEnabled; + } + + // `onPreExecute()` operates on the UI thread. + @Override + protected void onPreExecute() { + // Get a handle for the activity. + Activity activity = activityWeakReference.get(); + + // Abort if the activity is gone. + if ((activity==null) || activity.isFinishing()) { + return; + } + + // Get a handle for the no swipe view pager. + NoSwipeViewPager noSwipeViewPager = activity.findViewById(R.id.webviewpager); + + // Create a saving file snackbar. + savingFileSnackbar = Snackbar.make(noSwipeViewPager, R.string.saving_file, Snackbar.LENGTH_INDEFINITE); + + // Display the saving file snackbar. + savingFileSnackbar.show(); + } + + @Override + protected String doInBackground(String... urlToSave) { + // Get a handle for the activity. + Activity activity = activityWeakReference.get(); + + // Abort if the activity is gone. + if ((activity == null) || activity.isFinishing()) { + return null; + } + + // Define a save disposition string. + String saveDisposition = SUCCESS; + + // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`. + try { + // Get the URL from the main activity. + URL url = new URL(urlToSave[0]); + + // Open a connection to the URL. No data is actually sent at this point. + HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(); + + // Add the user agent to the header property. + httpUrlConnection.setRequestProperty("User-Agent", userAgent); + + // Add the cookies if they are enabled. + if (cookiesEnabled) { + // Get the cookies for the current domain. + String cookiesString = CookieManager.getInstance().getCookie(url.toString()); + + // Only add the cookies if they are not null. + if (cookiesString != null) { + // Add the cookies to the header property. + httpUrlConnection.setRequestProperty("Cookie", cookiesString); + } + } + + // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block. + try { + // Get the response code, which causes the connection to the server to be made. + httpUrlConnection.getResponseCode(); + + // Get the response body stream. + InputStream inputStream = new BufferedInputStream(httpUrlConnection.getInputStream()); + + // Get the file. + File file = new File(filePathString); + + // Delete the file if it exists. + if (file.exists()) { + //noinspection ResultOfMethodCallIgnored + file.delete(); + } + + // Create a new file. + //noinspection ResultOfMethodCallIgnored + file.createNewFile(); + + // Create an output file stream. + OutputStream outputStream = new FileOutputStream(file); + + // Initialize the conversion buffer byte array. + byte[] conversionBufferByteArray = new byte[1024]; + + // Define the buffer length variable. + int bufferLength; + + // Attempt to read data from the input stream and store it in the output stream. Also store the amount of data read in the buffer length variable. + while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) { // Proceed while the amount of data stored in the buffer in > 0. + // Write the contents of the conversion buffer to the output stream. + outputStream.write(conversionBufferByteArray, 0, bufferLength); + } + + // Close the input stream. + inputStream.close(); + + // Close the output stream. + outputStream.close(); + } finally { + // Disconnect the HTTP URL connection. + httpUrlConnection.disconnect(); + } + } catch (IOException exception) { + // Store the error in the save disposition string. + saveDisposition = exception.toString(); + } + + // Return the save disposition string. + return saveDisposition; + } + + // `onPostExecute()` operates on the UI thread. + @Override + protected void onPostExecute(String saveDisposition) { + // Get a handle for the activity. + Activity activity = activityWeakReference.get(); + + // Abort if the activity is gone. + if ((activity == null) || activity.isFinishing()) { + return; + } + + // Get a handle for the no swipe view pager. + NoSwipeViewPager noSwipeViewPager = activity.findViewById(R.id.webviewpager); + + // Dismiss the saving file snackbar. + savingFileSnackbar.dismiss(); + + // Display a save disposition snackbar. + if (saveDisposition.equals(SUCCESS)) { + Snackbar.make(noSwipeViewPager, R.string.file_saved, Snackbar.LENGTH_SHORT).show(); + } else { + Snackbar.make(noSwipeViewPager, activity.getString(R.string.error_saving_file) + " " + saveDisposition, Snackbar.LENGTH_INDEFINITE).show(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveLogcatDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveLogcatDialog.java index 9e27f529..dcd10f94 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveLogcatDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveLogcatDialog.java @@ -107,7 +107,7 @@ public class SaveLogcatDialog extends DialogFragment { } // Set the view. The parent view is null because it will be assigned by the alert dialog. - dialogBuilder.setView(activity.getLayoutInflater().inflate(R.layout.save_dialog, null)); + dialogBuilder.setView(activity.getLayoutInflater().inflate(R.layout.save_logcat_dialog, null)); // Set the cancel button listener. dialogBuilder.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> { diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java index 7e4250a0..c9e691f6 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java @@ -29,6 +29,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; @@ -69,12 +70,13 @@ public class SaveWebpageDialog extends DialogFragment { saveWebpageListener = (SaveWebpageListener) context; } - public static SaveWebpageDialog saveWebpage(int saveType) { + public static SaveWebpageDialog saveUrl(int saveType, String url) { // Create an arguments bundle. Bundle argumentsBundle = new Bundle(); - // Store the save type in the bundle. + // Store the arguments in the bundle. argumentsBundle.putInt("save_type", saveType); + argumentsBundle.putString("url", url); // Create a new instance of the save webpage dialog. SaveWebpageDialog saveWebpageDialog = new SaveWebpageDialog(); @@ -97,8 +99,9 @@ public class SaveWebpageDialog extends DialogFragment { // Remove the incorrect lint warning that the arguments might be null. assert arguments != null; - // Get the save type. + // Get the arguments from the bundle. int saveType = arguments.getInt("save_type"); + String url = arguments.getString("url"); // Get a handle for the activity and the context. Activity activity = getActivity(); @@ -125,11 +128,15 @@ public class SaveWebpageDialog extends DialogFragment { // Set the icon according to the save type. switch (saveType) { - case StoragePermissionDialog.SAVE_ARCHIVE: + case StoragePermissionDialog.SAVE: + dialogBuilder.setIcon(R.drawable.copy_enabled_dark); + break; + + case StoragePermissionDialog.SAVE_AS_ARCHIVE: dialogBuilder.setIcon(R.drawable.dom_storage_cleared_dark); break; - case StoragePermissionDialog.SAVE_IMAGE: + case StoragePermissionDialog.SAVE_AS_IMAGE: dialogBuilder.setIcon(R.drawable.images_enabled_dark); break; } @@ -139,11 +146,15 @@ public class SaveWebpageDialog extends DialogFragment { // Set the icon according to the save type. switch (saveType) { - case StoragePermissionDialog.SAVE_ARCHIVE: + case StoragePermissionDialog.SAVE: + dialogBuilder.setIcon(R.drawable.copy_enabled_light); + break; + + case StoragePermissionDialog.SAVE_AS_ARCHIVE: dialogBuilder.setIcon(R.drawable.dom_storage_cleared_light); break; - case StoragePermissionDialog.SAVE_IMAGE: + case StoragePermissionDialog.SAVE_AS_IMAGE: dialogBuilder.setIcon(R.drawable.images_enabled_light); break; } @@ -151,17 +162,21 @@ public class SaveWebpageDialog extends DialogFragment { // Set the title according to the type. switch (saveType) { - case StoragePermissionDialog.SAVE_ARCHIVE: + case StoragePermissionDialog.SAVE: + dialogBuilder.setTitle(R.string.save); + break; + + case StoragePermissionDialog.SAVE_AS_ARCHIVE: dialogBuilder.setTitle(R.string.save_archive); break; - case StoragePermissionDialog.SAVE_IMAGE: + case StoragePermissionDialog.SAVE_AS_IMAGE: dialogBuilder.setTitle(R.string.save_image); break; } // Set the view. The parent view is null because it will be assigned by the alert dialog. - dialogBuilder.setView(activity.getLayoutInflater().inflate(R.layout.save_dialog, null)); + dialogBuilder.setView(activity.getLayoutInflater().inflate(R.layout.save_webpage_dialog, null)); // Set the cancel button listener. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.cancel, null); @@ -187,12 +202,32 @@ public class SaveWebpageDialog extends DialogFragment { alertDialog.show(); // Get handles for the layout items. + EditText urlEditText = alertDialog.findViewById(R.id.url_edittext); EditText fileNameEditText = alertDialog.findViewById(R.id.file_name_edittext); Button browseButton = alertDialog.findViewById(R.id.browse_button); TextView fileExistsWarningTextView = alertDialog.findViewById(R.id.file_exists_warning_textview); TextView storagePermissionTextView = alertDialog.findViewById(R.id.storage_permission_textview); Button saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE); + // Update the status of the save button whe the URL changes. + urlEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + // Do nothing. + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + // Do nothing. + } + + @Override + public void afterTextChanged(Editable editable) { + // Enable the save button if the URL and file name are populated. + saveButton.setEnabled(!urlEditText.getText().toString().isEmpty() && !fileNameEditText.getText().toString().isEmpty()); + } + }); + // Update the status of the save button when the file name changes. fileNameEditText.addTextChangedListener(new TextWatcher() { @Override @@ -223,24 +258,43 @@ public class SaveWebpageDialog extends DialogFragment { } // Enable the save button if the file name is populated. - saveButton.setEnabled(!fileNameString.isEmpty()); + saveButton.setEnabled(!fileNameString.isEmpty() && !urlEditText.getText().toString().isEmpty()); } }); - // Create a default file name string. - String defaultFileName = ""; + // Create a file name string. + String fileName = ""; - // Set the default file name according to the type. + // Set the file name according to the type. switch (saveType) { - case StoragePermissionDialog.SAVE_ARCHIVE: - defaultFileName = getString(R.string.webpage_mht); + case StoragePermissionDialog.SAVE: + // Convert the URL to a URI. + Uri uri = Uri.parse(url); + + // Get the last path segment. + String lastPathSegment = uri.getLastPathSegment(); + + // Use a default file name if the last path segment is null. + if (lastPathSegment == null) { + lastPathSegment = getString(R.string.file); + } + + // Use the last path segment as the file name. + fileName = lastPathSegment; break; - case StoragePermissionDialog.SAVE_IMAGE: - defaultFileName = getString(R.string.webpage_png); + case StoragePermissionDialog.SAVE_AS_ARCHIVE: + fileName = getString(R.string.webpage_mht); + break; + + case StoragePermissionDialog.SAVE_AS_IMAGE: + fileName = getString(R.string.webpage_png); break; } + // Save the file name as the default file name. This must be final to be used in the lambda below. + final String defaultFileName = fileName; + // Create a string for the default file path. String defaultFilePath; @@ -256,7 +310,8 @@ public class SaveWebpageDialog extends DialogFragment { defaultFilePath = context.getExternalFilesDir(null) + "/" + defaultFileName; } - // Display the default file path. + // Populate the edit texts. + urlEditText.setText(url); fileNameEditText.setText(defaultFilePath); // Move the cursor to the end of the default file path. @@ -271,15 +326,7 @@ public class SaveWebpageDialog extends DialogFragment { browseIntent.setType("*/*"); // Set the initial file name according to the type. - switch (saveType) { - case StoragePermissionDialog.SAVE_ARCHIVE: - browseIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.webpage_mht)); - break; - - case StoragePermissionDialog.OPEN: - browseIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.webpage_png)); - break; - } + browseIntent.putExtra(Intent.EXTRA_TITLE, defaultFileName); // Set the initial directory if the minimum API >= 26. if (Build.VERSION.SDK_INT >= 26) { diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/StoragePermissionDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/StoragePermissionDialog.java index 010dbcf6..5b171548 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/StoragePermissionDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/StoragePermissionDialog.java @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2019 Soren Stoutner . + * Copyright © 2018-2020 Soren Stoutner . * * This file is part of Privacy Browser . * @@ -36,8 +36,9 @@ import com.stoutner.privacybrowser.R; public class StoragePermissionDialog extends DialogFragment { // Define the save type constants. public static final int OPEN = 0; - public static final int SAVE_ARCHIVE = 1; - public static final int SAVE_IMAGE = 2; + public static final int SAVE = 1; + public static final int SAVE_AS_ARCHIVE = 2; + public static final int SAVE_AS_IMAGE = 3; // The listener is used in `onAttach()` and `onCreateDialog()`. private StoragePermissionDialogListener storagePermissionDialogListener; diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/FileNameHelper.java b/app/src/main/java/com/stoutner/privacybrowser/helpers/FileNameHelper.java index 5eb19484..980a838d 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/helpers/FileNameHelper.java +++ b/app/src/main/java/com/stoutner/privacybrowser/helpers/FileNameHelper.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Soren Stoutner . + * Copyright © 2019-2020 Soren Stoutner . * * This file is part of Privacy Browser . * @@ -38,7 +38,7 @@ public class FileNameHelper { String fileNameContentPath = rawFileNamePath.substring(0, rawFileNamePath.indexOf(":")); String fileNameFinalPath = rawFileNamePath.substring(rawFileNamePath.indexOf(":") + 1); - // Check to see if the current file name final patch is a complete, valid path + // Check to see if the current file name final patch is a complete, valid path. if (fileNameFinalPath.startsWith("/storage/emulated/")) { // The existing file name final path is a complete, valid path. // Use the provided file name path as is. fileNamePath = fileNameFinalPath; diff --git a/app/src/main/res/drawable/copy_dark.xml b/app/src/main/res/drawable/copy_dark.xml index f37115ab..50c9da76 100644 --- a/app/src/main/res/drawable/copy_dark.xml +++ b/app/src/main/res/drawable/copy_dark.xml @@ -1,4 +1,4 @@ - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/copy_enabled_light.xml b/app/src/main/res/drawable/copy_enabled_light.xml new file mode 100644 index 00000000..0149278e --- /dev/null +++ b/app/src/main/res/drawable/copy_enabled_light.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/copy_light.xml b/app/src/main/res/drawable/copy_light.xml index 1c2dc993..0c33ca1b 100644 --- a/app/src/main/res/drawable/copy_light.xml +++ b/app/src/main/res/drawable/copy_light.xml @@ -1,4 +1,4 @@ - + + - + diff --git a/app/src/main/res/drawable/custom_user_agent_enabled_light.xml b/app/src/main/res/drawable/custom_user_agent_enabled_light.xml index 39a5b001..9a39a93f 100644 --- a/app/src/main/res/drawable/custom_user_agent_enabled_light.xml +++ b/app/src/main/res/drawable/custom_user_agent_enabled_light.xml @@ -1,4 +1,4 @@ - + - + diff --git a/app/src/main/res/drawable/custom_user_agent_ghosted_dark.xml b/app/src/main/res/drawable/custom_user_agent_ghosted_dark.xml index 232278a8..2e16df62 100644 --- a/app/src/main/res/drawable/custom_user_agent_ghosted_dark.xml +++ b/app/src/main/res/drawable/custom_user_agent_ghosted_dark.xml @@ -1,4 +1,4 @@ - + - + diff --git a/app/src/main/res/drawable/custom_user_agent_ghosted_light.xml b/app/src/main/res/drawable/custom_user_agent_ghosted_light.xml index f2a6dd5e..808d7f61 100644 --- a/app/src/main/res/drawable/custom_user_agent_ghosted_light.xml +++ b/app/src/main/res/drawable/custom_user_agent_ghosted_light.xml @@ -1,4 +1,4 @@ - + - + diff --git a/app/src/main/res/layout/save_dialog.xml b/app/src/main/res/layout/save_dialog.xml deleted file mode 100644 index 5615013b..00000000 --- a/app/src/main/res/layout/save_dialog.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - -