diff --git a/app/src/main/assets/app/applySettings.js b/app/src/main/assets/app/applySettings.js index faaa87ecd..d5f5f026d 100644 --- a/app/src/main/assets/app/applySettings.js +++ b/app/src/main/assets/app/applySettings.js @@ -22,9 +22,12 @@ function handleStorageChange(event) { break; case 'selectedMainWeatherProvider': setTimeout(() => { - window.location.reload(); - sendThemeToAndroid('overcast') - }, 1500); + if(localStorage.getItem('selectedMainWeatherProvider') === 'Accuweather' && !localStorage.getItem('ApiForAccu')){ + } else{ + window.location.reload(); + sendThemeToAndroid('overcast') + } + }, 1500); break; case 'ApiForAccu': setTimeout(() => { @@ -53,14 +56,18 @@ window.addEventListener('storage', handleStorageChange); // apply when options are changed -function HandleNoReloadSettings() { +let debounceTimer; - const renderLocation = localStorage.getItem('CurrentLocationName') - setTimeout(() =>{ - LoadLocationOnRequest(localStorage.getItem('currentLat'), localStorage.getItem('currentLong'), renderLocation) - onAllLocationsLoaded() - }, 300); - } +function HandleNoReloadSettings() { + const renderLocation = localStorage.getItem('CurrentLocationName'); + + clearTimeout(debounceTimer); + + debounceTimer = setTimeout(() => { + LoadLocationOnRequest(localStorage.getItem('currentLat'), localStorage.getItem('currentLong'), renderLocation); + onAllLocationsLoaded(); + }, 500); +} function RemoveDisplayedAnimations() { diff --git a/app/src/main/assets/pages/settings.html b/app/src/main/assets/pages/settings.html index 90cb97fea..af6e936e8 100644 --- a/app/src/main/assets/pages/settings.html +++ b/app/src/main/assets/pages/settings.html @@ -324,6 +324,20 @@ open_in_new + + upgrade +
Export data
+
Export all your settings, locations, and their last fetched weather data—literally everything!
+
+ + + + download +
Import data
+
Warning: Importing will erase your current app data
+
+ + @@ -1093,6 +1107,59 @@ function ChangeSelectedMainProviderName() { } else{ document.getElementById('useWeatherAlerts').selected = true } + + document.getElementById("exportAppData").addEventListener("click", () => { + + + + const data = { ...localStorage }; + + // Check if local storage has data + if (Object.keys(data).length > 0) { + // Convert data to JSON string + const jsonData = JSON.stringify(data, null, 2); + + // Use Android interface to save the file + Android.saveFile(jsonData); + } else { + // Show toast message if no data is found + ShowSnackMessage.ShowSnack("No data found", "short"); + } +}); + +document.getElementById('importAppData').addEventListener('click', () => { + Android.importFile(); +}); + +function handleImportedData(importedData) { + try { + const data = JSON.parse(importedData); + + if (typeof data !== "object" || data === null) { + throw new Error("Invalid data format"); + } + + localStorage.clear(); + + + setTimeout(() => { + for (const [key, value] of Object.entries(data)) { + localStorage.setItem(key, value); + + } + ShowSnackMessage.ShowSnack("Data imported successfully!", "short"); + +}, 1000); + setTimeout(() => { + Android.reloadTheApp(); +}, 1500); + + } catch (error) { + ShowSnackMessage.ShowSnack("Error parsing the imported JSON data. Please ensure it is valid and correctly formatted.", "long"); + console.error(error); + } +} + diff --git a/app/src/main/java/com/example/weathermaster/SettingsActivity.java b/app/src/main/java/com/example/weathermaster/SettingsActivity.java index 4d211ea30..be5b7ae75 100644 --- a/app/src/main/java/com/example/weathermaster/SettingsActivity.java +++ b/app/src/main/java/com/example/weathermaster/SettingsActivity.java @@ -21,6 +21,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.webkit.JavascriptInterface; +import android.webkit.ValueCallback; +import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; @@ -30,6 +32,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; @@ -38,9 +41,26 @@ import androidx.core.content.res.ResourcesCompat; import com.google.android.material.snackbar.Snackbar; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStream; + public class SettingsActivity extends AppCompatActivity { private WebView webview; + + private ValueCallback filePathCallback; + private final static int FILECHOOSER_RESULTCODE = 1; + private final static int EXPORT_REQUEST_CODE = 2; + private static final int PERMISSION_REQUEST_CODE = 1; + private static final int SAVE_DOCUMENT_REQUEST_CODE = 2; + private static final int IMPORT_REQUEST_CODE = 3; + + private String dataToSave; private boolean isFirstLoad = true; public void onBackPressed() { @@ -75,6 +95,7 @@ public class SettingsActivity extends AppCompatActivity { webview.addJavascriptInterface(androidInterface, "AndroidInterface"); webview.addJavascriptInterface(new ShowToastInterface(this), "ToastAndroidShow"); webview.addJavascriptInterface(new ShowSnackInterface(this), "ShowSnackMessage"); + webview.addJavascriptInterface(new WebAppInterface(), "Android"); webview.setBackgroundColor(getResources().getColor(R.color.yellowBg)); webview.loadUrl("file:///android_asset/pages/settings.html"); @@ -107,12 +128,141 @@ public class SettingsActivity extends AppCompatActivity { webview.evaluateJavascript(jsCodePrimaryColor, null); } } - }); + }); + } + + + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == FILECHOOSER_RESULTCODE) { + if (filePathCallback != null) { + Uri[] results = null; + if (resultCode == RESULT_OK && data != null) { + String dataString = data.getDataString(); + if (dataString != null) { + results = new Uri[]{Uri.parse(dataString)}; + } + } + filePathCallback.onReceiveValue(results); + filePathCallback = null; + } + } else if (requestCode == SAVE_DOCUMENT_REQUEST_CODE) { + if (resultCode == RESULT_OK && data != null && data.getData() != null) { + saveToUri(data.getData()); + } else { + Toast.makeText(this, "Error exporting", Toast.LENGTH_SHORT).show(); + } + } else if (requestCode == IMPORT_REQUEST_CODE) { + if (resultCode == RESULT_OK && data != null && data.getData() != null) { + importFromUri(data.getData()); + } else { + Toast.makeText(this, "Error importing file", Toast.LENGTH_SHORT).show(); + } + } + } + + + private void restartApp() { + Toast.makeText(this, "App is restarting...", Toast.LENGTH_SHORT).show(); + + Intent intent = new Intent(getApplicationContext(), MainActivity.class); + + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + + startActivity(intent); + + finish(); } + + public class WebAppInterface { + @JavascriptInterface + public void saveFile(String data) { + dataToSave = data; + openFilePickerExport(); + } + + @JavascriptInterface + public void importFile() { + openFilePickerImport(); // Open file picker for importing JSON data + } + + @JavascriptInterface + public void reloadTheApp() { + restartApp(); // Open file picker for importing JSON data + } + } + + + + + + private void openFilePickerExport() { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/json"); + intent.putExtra(Intent.EXTRA_TITLE, "WeatherMasterData.json"); + startActivityForResult(intent, SAVE_DOCUMENT_REQUEST_CODE); + } + + private void openFilePickerImport() { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/json"); + startActivityForResult(intent, IMPORT_REQUEST_CODE); + } + + private void importFromUri(Uri uri) { + try { + InputStream inputStream = getContentResolver().openInputStream(uri); + if (inputStream != null) { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder stringBuilder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + } + reader.close(); + inputStream.close(); + + String importedData = stringBuilder.toString(); + + String escapedData = importedData.replace("'", "\\'").replace("\"", "\\\""); + + runOnUiThread(() -> { + String jsCode = "handleImportedData('" + escapedData + "');"; + webview.evaluateJavascript(jsCode, null); + }); + } + } catch (Exception e) { + Toast.makeText(this, "Error reading file", Toast.LENGTH_LONG).show(); + e.printStackTrace(); + } + } + + + + private void saveToUri(Uri uri) { + try { + OutputStream outputStream = getContentResolver().openOutputStream(uri); + if (outputStream != null) { + outputStream.write(dataToSave.getBytes()); + outputStream.close(); + Toast.makeText(this, "Backup saved", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, "Error", Toast.LENGTH_SHORT).show(); + } + } catch (Exception e) { + Toast.makeText(this, "Error saving file", Toast.LENGTH_LONG).show(); + e.printStackTrace(); + } + } private void requestNotificationPermissions() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) @@ -201,6 +351,10 @@ public class SettingsActivity extends AppCompatActivity { } } +// export or import data + + + public class AndroidInterface { private SettingsActivity sActivity;