diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml
index 234488dd1..325a58062 100644
--- a/.idea/androidTestResultsUserPreferences.xml
+++ b/.idea/androidTestResultsUserPreferences.xml
@@ -26,11 +26,13 @@
+
+
@@ -87,6 +89,7 @@
diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml
index f6b25a43e..625465062 100644
--- a/.idea/appInsightsSettings.xml
+++ b/.idea/appInsightsSettings.xml
@@ -28,9 +28,9 @@
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index 077363a23..56da0270c 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -14,6 +14,9 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/other.xml b/.idea/other.xml
index 1b1a2c599..94c96f631 100644
--- a/.idea/other.xml
+++ b/.idea/other.xml
@@ -113,6 +113,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -124,6 +135,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/runConfigurations/_app_androidTest.xml b/.idea/runConfigurations/_app_androidTest.xml
index 37e91ab62..302f5aede 100755
--- a/.idea/runConfigurations/_app_androidTest.xml
+++ b/.idea/runConfigurations/_app_androidTest.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/.idea/runConfigurations/app.xml b/.idea/runConfigurations/app.xml
new file mode 100644
index 000000000..5e37af071
--- /dev/null
+++ b/.idea/runConfigurations/app.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/QuoteUnquote.cloudLib b/QuoteUnquote.cloudLib
index 6aa6da5f6..a1c750d0e 160000
--- a/QuoteUnquote.cloudLib
+++ b/QuoteUnquote.cloudLib
@@ -1 +1 @@
-Subproject commit 6aa6da5f6a78d8df069b0701a7529af64eca9807
+Subproject commit a1c750d0e4010d1d2666c69bd3afcd28f074a4df
diff --git a/app/build.gradle b/app/build.gradle
index 5a74b32c3..9e69f48fa 100755
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -3,6 +3,7 @@ apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'kotlin-android'
apply plugin: 'com.diffplug.spotless'
+apply plugin: "org.jetbrains.kotlin.plugin.compose"
apply from: '../jacoco.gradle'
apply from: '../ktlint.gradle'
@@ -46,9 +47,9 @@ android {
applicationId "com.github.jameshnsears.quoteunquote"
// changelog version | min sdk | target sdk
- versionCode 1762434
+ versionCode 1772434
// semantic versioning
- versionName "4.43.0"
+ versionName "4.43.1"
vectorDrawables.useSupportLibrary = true
@@ -63,6 +64,20 @@ android {
}
}
+ buildFeatures {
+ compose true
+ }
+
+ compose {
+ codeGeneration {
+ previewMode = PreviewMode.ON
+ }
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion '1.5.1'
+ }
+
packagingOptions {
resources {
excludes += [
@@ -227,7 +242,7 @@ dependencies {
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.core:core-ktx:1.13.1'
- implementation 'androidx.databinding:databinding-runtime:8.5.2'
+ implementation 'androidx.databinding:databinding-runtime:8.6.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.room:room-guava:2.6.1'
implementation 'androidx.room:room-runtime:2.6.1'
@@ -250,6 +265,16 @@ dependencies {
implementation project(path: ':cloudLib')
implementation project(path: ':utilsLib')
+ implementation platform('androidx.compose:compose-bom:2024.08.00')
+ debugImplementation 'androidx.compose.ui:ui-tooling-preview'
+ implementation 'androidx.activity:activity-compose'
+ implementation 'androidx.compose.material3:material3'
+ implementation 'androidx.compose.material:material'
+ implementation 'androidx.compose.ui:ui'
+ implementation 'androidx.compose.ui:ui-tooling'
+ implementation 'androidx.core:core-ktx'
+ implementation 'androidx.lifecycle:lifecycle-runtime-ktx'
+
testImplementation 'androidx.arch.core:core-testing:2.2.0'
testImplementation 'androidx.room:room-testing:2.6.1'
testImplementation 'androidx.test:core-ktx:1.6.1'
diff --git a/app/src/androidTest/java/com/github/jameshnsears/quoteunquote/utils/ImportHelperTest.kt b/app/src/androidTest/java/com/github/jameshnsears/quoteunquote/utils/ImportHelperTest.kt
index 3d57f40ca..da3a166da 100755
--- a/app/src/androidTest/java/com/github/jameshnsears/quoteunquote/utils/ImportHelperTest.kt
+++ b/app/src/androidTest/java/com/github/jameshnsears/quoteunquote/utils/ImportHelperTest.kt
@@ -2,6 +2,7 @@ package com.github.jameshnsears.quoteunquote.utils
import androidx.test.platform.app.InstrumentationRegistry
import com.github.jameshnsears.quoteunquote.QuoteUnquoteModelUtility
+import com.github.jameshnsears.quoteunquote.utils.widget.WidgetIdHelper
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.fail
import org.junit.Test
@@ -17,6 +18,13 @@ class ImportHelperTest : QuoteUnquoteModelUtility() {
val quotationEntityLinkedHashSet = importHelper.csvImportDatabase(inputStream)
assertEquals(762, quotationEntityLinkedHashSet.size)
+
+ quoteUnquoteModelDouble.insertQuotationsExternal(quotationEntityLinkedHashSet)
+ quoteUnquoteModelDouble.setDefault(WidgetIdHelper.WIDGET_ID_01, ContentSelection.ALL)
+ assertEquals(
+ "00000000",
+ quoteUnquoteModelDouble.databaseRepository!!.previous[0].digest,
+ )
}
@Test
diff --git a/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/notifications/NotificationsFragment.java b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/notifications/NotificationsFragment.java
index 214c56a41..02c2bff43 100755
--- a/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/notifications/NotificationsFragment.java
+++ b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/notifications/NotificationsFragment.java
@@ -143,6 +143,7 @@ public void onViewCreated(
private void handleSpecialPermissionForExactAlarm() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ fragmentNotificationsBinding.textViewExactTimeWarningDivider.setVisibility(VISIBLE);
fragmentNotificationsBinding.textViewExactTimeWarningInfo.setVisibility(VISIBLE);
fragmentNotificationsBinding.textViewExactTimeWarning.setVisibility(VISIBLE);
@@ -159,6 +160,7 @@ private void handleSpecialPermissionForExactAlarm() {
} else {
handleSpecialPermissionForExactAlarmCommon();
+ fragmentNotificationsBinding.textViewExactTimeWarningDivider.setVisibility(View.GONE);
fragmentNotificationsBinding.textViewExactTimeWarningInfo.setVisibility(View.GONE);
fragmentNotificationsBinding.textViewExactTimeWarning.setVisibility(View.GONE);
}
diff --git a/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/QuotationsFragmentStateAdapter.java b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/QuotationsFragmentStateAdapter.java
index e708fdeca..2ae1471ea 100755
--- a/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/QuotationsFragmentStateAdapter.java
+++ b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/QuotationsFragmentStateAdapter.java
@@ -4,7 +4,7 @@
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
-import com.github.jameshnsears.quoteunquote.configure.fragment.quotations.tabs.database.QuotationsDatabaseFragment;
+import com.github.jameshnsears.quoteunquote.configure.fragment.quotations.tabs.content.QuotationsContentFragment;
import com.github.jameshnsears.quoteunquote.configure.fragment.quotations.tabs.filter.QuotationsFilterFragment;
public class QuotationsFragmentStateAdapter extends FragmentStateAdapter {
@@ -24,7 +24,7 @@ public Fragment createFragment(final int pos) {
return quotationsFilterFragment = QuotationsFilterFragment.newInstance(widgetId);
default:
- return QuotationsDatabaseFragment.newInstance(widgetId);
+ return QuotationsContentFragment.newInstance(widgetId);
}
}
diff --git a/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/QuotationsContentFragment.java b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/QuotationsContentFragment.java
new file mode 100755
index 000000000..dd63231a8
--- /dev/null
+++ b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/QuotationsContentFragment.java
@@ -0,0 +1,119 @@
+package com.github.jameshnsears.quoteunquote.configure.fragment.quotations.tabs.content;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+
+import com.github.jameshnsears.quoteunquote.QuoteUnquoteModel;
+import com.github.jameshnsears.quoteunquote.R;
+import com.github.jameshnsears.quoteunquote.configure.fragment.FragmentCommon;
+import com.github.jameshnsears.quoteunquote.configure.fragment.quotations.QuotationsPreferences;
+import com.github.jameshnsears.quoteunquote.configure.fragment.quotations.tabs.content.tabs.ContentCsvFragment;
+import com.github.jameshnsears.quoteunquote.configure.fragment.quotations.tabs.content.tabs.ContentInternalFragment;
+import com.github.jameshnsears.quoteunquote.configure.fragment.quotations.tabs.content.tabs.ContentWebFragment;
+import com.github.jameshnsears.quoteunquote.databinding.FragmentQuotationsTabDatabaseBinding;
+import com.google.android.material.tabs.TabLayout;
+
+@Keep
+public class QuotationsContentFragment extends FragmentCommon {
+ @Nullable
+ public FragmentQuotationsTabDatabaseBinding fragmentQuotationsTabDatabaseBinding;
+
+ @Nullable
+ public QuoteUnquoteModel quoteUnquoteModel;
+
+ @Nullable
+ public QuotationsPreferences quotationsPreferences;
+
+ public QuotationsContentFragment(int widgetId) {
+ super(widgetId);
+ }
+
+ @NonNull
+ public static QuotationsContentFragment newInstance(
+ int widgetId) {
+ QuotationsContentFragment fragment = new QuotationsContentFragment(widgetId);
+ fragment.setArguments(null);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(@NonNull Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ quoteUnquoteModel = new QuoteUnquoteModel(widgetId, getContext());
+ }
+
+ @Override
+ @NonNull
+ public View onCreateView(
+ @NonNull LayoutInflater inflater,
+ @NonNull ViewGroup container,
+ @NonNull Bundle savedInstanceState) {
+ this.quotationsPreferences = new QuotationsPreferences(this.widgetId, this.getContext());
+
+ this.fragmentQuotationsTabDatabaseBinding = FragmentQuotationsTabDatabaseBinding.inflate(this.getLayoutInflater());
+ return this.fragmentQuotationsTabDatabaseBinding.getRoot();
+ }
+
+ @Override
+ public void onViewCreated(
+ @NonNull View view, @NonNull Bundle savedInstanceState) {
+
+ loadFragment(new ContentInternalFragment(widgetId));
+
+ fragmentQuotationsTabDatabaseBinding.tabDatabase.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
+ @Override
+ public void onTabSelected(TabLayout.Tab tab) {
+ Fragment selectedFragment = null;
+ switch (tab.getPosition()) {
+ case 0:
+ selectedFragment = new ContentInternalFragment(widgetId);
+ break;
+ case 1:
+ selectedFragment = new ContentCsvFragment(widgetId);
+ break;
+ case 2:
+ selectedFragment = new ContentWebFragment(widgetId);
+ break;
+ }
+
+ if (selectedFragment != null) {
+ loadFragment(selectedFragment);
+ }
+ }
+
+ @Override
+ public void onTabUnselected(TabLayout.Tab tab) {
+ // Handle tab unselected if needed
+ }
+
+ @Override
+ public void onTabReselected(TabLayout.Tab tab) {
+ // Handle tab reselected if needed
+ }
+ });
+ }
+
+ private void loadFragment(Fragment fragment) {
+ FragmentManager fragmentManager = getChildFragmentManager();
+ FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+ fragmentTransaction.replace(R.id.frame_layout, fragment);
+ fragmentTransaction.commit();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+
+ this.fragmentQuotationsTabDatabaseBinding = null;
+ }
+}
diff --git a/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentCsvComposable.kt b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentCsvComposable.kt
new file mode 100644
index 000000000..b1174e183
--- /dev/null
+++ b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentCsvComposable.kt
@@ -0,0 +1,34 @@
+package com.github.jameshnsears.quoteunquote.configure.fragment.quotations.tabs.content.tabs
+
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.tooling.preview.Preview
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+@Composable
+fun Greeting(
+ stateFlow: StateFlow,
+ name: String,
+) {
+ val state = stateFlow.collectAsState()
+
+ if (true == false) {
+ Text(
+ text = "Hello, $name!",
+ )
+ }
+}
+
+@Preview(apiLevel = 34)
+@Composable
+fun PreviewGreeting() {
+ val _stateFlow = MutableStateFlow(true)
+ val stateFlow: StateFlow = _stateFlow
+
+ Greeting(
+ stateFlow,
+ name = "World",
+ )
+}
diff --git a/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentCsvFragment.kt b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentCsvFragment.kt
new file mode 100644
index 000000000..37154438a
--- /dev/null
+++ b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentCsvFragment.kt
@@ -0,0 +1,243 @@
+package com.github.jameshnsears.quoteunquote.configure.fragment.quotations.tabs.content.tabs
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.os.ParcelFileDescriptor
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.CompoundButton
+import android.widget.Toast
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.ui.platform.ViewCompositionStrategy
+import com.github.jameshnsears.quoteunquote.R
+import com.github.jameshnsears.quoteunquote.configure.ConfigureActivity
+import com.github.jameshnsears.quoteunquote.configure.fragment.quotations.QuotationsPreferences
+import com.github.jameshnsears.quoteunquote.database.DatabaseRepository
+import com.github.jameshnsears.quoteunquote.databinding.FragmentQuotationsTabDatabaseTabCsvBinding
+import com.github.jameshnsears.quoteunquote.utils.ImportHelper
+import com.github.jameshnsears.quoteunquote.utils.ImportHelper.ImportHelperException
+import com.google.android.material.snackbar.BaseTransientBottomBar
+import com.google.android.material.snackbar.Snackbar
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import timber.log.Timber
+import java.io.FileInputStream
+import java.io.FileNotFoundException
+import java.io.IOException
+
+class ContentCsvFragment(widgetId: Int) : ContentFragment(widgetId) {
+ private var _binding: FragmentQuotationsTabDatabaseTabCsvBinding? = null
+
+ private val fragmentQuotationsTabDatabaseTabCsvBinding get() = _binding!!
+
+ var quotationsPreferences: QuotationsPreferences? = null
+
+ private var storageAccessFrameworkActivityResultCSV: ActivityResultLauncher? = null
+
+ private val _stateFlow = MutableStateFlow(false)
+ val stateFlow: StateFlow = _stateFlow
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ this.quotationsPreferences = QuotationsPreferences(
+ this.widgetId,
+ this.requireContext(),
+ )
+
+ // https://developer.android.com/develop/ui/compose/migrate/strategy
+ // https://developer.android.com/codelabs/basic-android-kotlin-training-compose-add-compose-to-a-view-based-app
+ // https://developer.android.com/develop/ui/compose/migrate/interoperability-apis/compose-in-views
+ _binding = FragmentQuotationsTabDatabaseTabCsvBinding.inflate(inflater, container, false)
+ val view = fragmentQuotationsTabDatabaseTabCsvBinding.root
+ fragmentQuotationsTabDatabaseTabCsvBinding.composeViewCsv.apply {
+ setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+
+ _stateFlow.value = quotationsPreferences!!.databaseExternalCsv
+ setContent {
+ MaterialTheme {
+ Greeting(
+ stateFlow,
+ ":-)",
+ )
+ }
+ }
+ }
+ return view
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ Timber.d("onViewCreated..ContentCsvFragment")
+
+ if (quotationsPreferences!!.databaseExternalCsv) {
+ fragmentQuotationsTabDatabaseTabCsvBinding.radioButtonDatabaseExternalCsv.isEnabled =
+ true
+ fragmentQuotationsTabDatabaseTabCsvBinding.radioButtonDatabaseExternalCsv.isChecked =
+ true
+ } else {
+ fragmentQuotationsTabDatabaseTabCsvBinding.radioButtonDatabaseExternalCsv.isEnabled =
+ false
+ fragmentQuotationsTabDatabaseTabCsvBinding.radioButtonDatabaseExternalCsv.isChecked =
+ false
+ }
+
+ createListenerRadioCsv()
+
+ setHandleImportCsv()
+
+ createListenerButtonImportCsv()
+ }
+ private fun createListenerRadioCsv() {
+ val radioButtonDatabaseInternal =
+ fragmentQuotationsTabDatabaseTabCsvBinding.radioButtonDatabaseExternalCsv
+ radioButtonDatabaseInternal.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
+ usingCsv()
+ }
+ }
+
+ private fun usingCsv() {
+ quotationsPreferences!!.databaseInternal = false
+ quotationsPreferences!!.databaseExternalCsv = true
+ quotationsPreferences!!.databaseExternalWeb = false
+ quotationsPreferences!!.databaseExternalContent = QuotationsPreferences.DATABASE_EXTERNAL
+ DatabaseRepository.useInternalDatabase = false
+
+ updateQuotationsUI()
+ }
+
+ protected fun createListenerButtonImportCsv() {
+ // adb push app/src/androidTest/assets/Favourites.csv /sdcard/Download
+
+ // invoke Storage Access Framework
+ fragmentQuotationsTabDatabaseTabCsvBinding.buttonImport.setOnClickListener { v ->
+ if (fragmentQuotationsTabDatabaseTabCsvBinding.buttonImport.isEnabled) {
+ ConfigureActivity.launcherInvoked = true
+
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
+ intent.addCategory(Intent.CATEGORY_OPENABLE)
+ intent.setType("text/comma-separated-values")
+ this.storageAccessFrameworkActivityResultCSV!!.launch(intent)
+ }
+ }
+ }
+
+ private fun setHandleImportCsv() {
+ // default: /storage/emulated/0/Download/
+ this.storageAccessFrameworkActivityResultCSV =
+ this.registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult(),
+ ) { activityResult: ActivityResult ->
+ Timber.d("%d", activityResult.resultCode)
+ val toast = Toast.makeText(
+ this.context,
+ this.requireContext()
+ .getString(R.string.fragment_quotations_database_import_importing),
+ Toast.LENGTH_SHORT,
+ )
+
+ if (Activity.RESULT_OK == activityResult.resultCode) {
+ toast.show()
+
+ var parcelFileDescriptor: ParcelFileDescriptor? = null
+ var fileInputStream: FileInputStream? = null
+
+ try {
+ parcelFileDescriptor =
+ this.requireContext().contentResolver.openFileDescriptor(
+ activityResult.data!!.data!!, "r",
+ )
+ fileInputStream = FileInputStream(parcelFileDescriptor!!.fileDescriptor)
+
+ val importHelper = ImportHelper()
+ val quotations =
+ importHelper.csvImportDatabase(fileInputStream)
+ quoteUnquoteModel!!.insertQuotationsExternal(quotations)
+
+ fragmentQuotationsTabDatabaseTabCsvBinding.radioButtonDatabaseExternalCsv.isEnabled =
+ true
+ fragmentQuotationsTabDatabaseTabCsvBinding.radioButtonDatabaseExternalCsv.isChecked =
+ true
+
+ importWasSuccessful()
+
+ toast.cancel()
+ Toast.makeText(
+ this.context,
+ this.requireContext()
+ .getString(R.string.fragment_quotations_database_import_success),
+ Toast.LENGTH_SHORT,
+ ).show()
+ } catch (e: ImportHelperException) {
+ toast.cancel()
+
+ fragmentQuotationsTabDatabaseTabCsvBinding.radioButtonDatabaseExternalCsv.isEnabled =
+ false
+ fragmentQuotationsTabDatabaseTabCsvBinding.radioButtonDatabaseExternalCsv.isChecked =
+ false
+
+ useInternalDatabase()
+
+ var message = ""
+ message = if (-1 == e.lineNumber) {
+ this.requireContext().getString(
+ R.string.fragment_quotations_database_import_contents_0,
+ e.message,
+ )
+ } else {
+ this.requireContext().getString(
+ R.string.fragment_quotations_database_import_contents_1,
+ e.lineNumber,
+ e.message,
+ )
+ }
+
+ Snackbar.make(
+ fragmentQuotationsTabDatabaseTabCsvBinding.root,
+ message,
+ BaseTransientBottomBar.LENGTH_LONG,
+ ).show()
+ } catch (e: FileNotFoundException) {
+ toast.cancel()
+
+ fragmentQuotationsTabDatabaseTabCsvBinding.radioButtonDatabaseExternalCsv.isEnabled =
+ false
+ fragmentQuotationsTabDatabaseTabCsvBinding.radioButtonDatabaseExternalCsv.isChecked =
+ false
+
+ useInternalDatabase()
+
+ Snackbar.make(
+ fragmentQuotationsTabDatabaseTabCsvBinding.root,
+ this.requireContext().getString(
+ R.string.fragment_quotations_database_import_contents_0,
+ e.message,
+ ),
+ BaseTransientBottomBar.LENGTH_LONG,
+ ).show()
+ } finally {
+ _stateFlow.value = quotationsPreferences!!.databaseExternalCsv
+
+ try {
+ fileInputStream?.close()
+ parcelFileDescriptor?.close()
+ } catch (e: IOException) {
+ Timber.e(e.message)
+ }
+ }
+ }
+ ConfigureActivity.launcherInvoked = false
+ }
+ }
+}
diff --git a/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentFragment.java b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentFragment.java
new file mode 100755
index 000000000..c8b2680a6
--- /dev/null
+++ b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentFragment.java
@@ -0,0 +1,56 @@
+package com.github.jameshnsears.quoteunquote.configure.fragment.quotations.tabs.content.tabs;
+
+import android.os.Bundle;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.github.jameshnsears.quoteunquote.QuoteUnquoteModel;
+import com.github.jameshnsears.quoteunquote.configure.fragment.FragmentCommon;
+import com.github.jameshnsears.quoteunquote.configure.fragment.quotations.QuotationsPreferences;
+import com.github.jameshnsears.quoteunquote.database.DatabaseRepository;
+import com.github.jameshnsears.quoteunquote.utils.ContentSelection;
+
+@Keep
+public class ContentFragment extends FragmentCommon {
+ @Nullable
+ public QuoteUnquoteModel quoteUnquoteModel;
+
+ @Nullable
+ public QuotationsPreferences quotationsPreferences;
+
+ public ContentFragment(int widgetId) {
+ super(widgetId);
+ }
+
+ @Override
+ public void onCreate(@NonNull Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ quoteUnquoteModel = new QuoteUnquoteModel(widgetId, getContext());
+ quotationsPreferences = new QuotationsPreferences(this.widgetId, this.getContext());
+ }
+
+ protected void updateQuotationsUI() {
+ QuotationsPreferences quotationsPreferences = new QuotationsPreferences(widgetId, getContext());
+ quotationsPreferences.setContentSelection(ContentSelection.ALL);
+ }
+
+ protected void importWasSuccessful() {
+ quotationsPreferences.setContentSelectionSearchCount(0);
+ quotationsPreferences.setContentSelectionSearch("");
+
+ DatabaseRepository.useInternalDatabase = false;
+
+ updateQuotationsUI();
+ }
+
+ protected void useInternalDatabase() {
+ this.quotationsPreferences.setDatabaseInternal(true);
+ this.quotationsPreferences.setDatabaseExternalCsv(false);
+ this.quotationsPreferences.setDatabaseExternalWeb(false);
+ DatabaseRepository.useInternalDatabase = true;
+
+ updateQuotationsUI();
+ }
+}
diff --git a/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentInternalFragment.java b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentInternalFragment.java
new file mode 100755
index 000000000..3561e9cc3
--- /dev/null
+++ b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentInternalFragment.java
@@ -0,0 +1,70 @@
+package com.github.jameshnsears.quoteunquote.configure.fragment.quotations.tabs.content.tabs;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RadioButton;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.github.jameshnsears.quoteunquote.configure.fragment.quotations.QuotationsPreferences;
+import com.github.jameshnsears.quoteunquote.database.DatabaseRepository;
+import com.github.jameshnsears.quoteunquote.databinding.FragmentQuotationsTabDatabaseTabInternalBinding;
+
+import timber.log.Timber;
+
+@Keep
+public class ContentInternalFragment extends ContentFragment {
+ @Nullable
+ public FragmentQuotationsTabDatabaseTabInternalBinding fragmentQuotationsTabDatabaseTabInternalBinding;
+
+ @Nullable
+ public QuotationsPreferences quotationsPreferences;
+
+ public ContentInternalFragment(int widgetId) {
+ super(widgetId);
+ }
+
+ @Override
+ @NonNull
+ public View onCreateView(
+ @NonNull LayoutInflater inflater,
+ @NonNull ViewGroup container,
+ @NonNull Bundle savedInstanceState) {
+ this.quotationsPreferences = new QuotationsPreferences(this.widgetId, this.getContext());
+
+ this.fragmentQuotationsTabDatabaseTabInternalBinding = FragmentQuotationsTabDatabaseTabInternalBinding.inflate(this.getLayoutInflater());
+ return this.fragmentQuotationsTabDatabaseTabInternalBinding.getRoot();
+ }
+
+ @Override
+ public void onViewCreated(
+ @NonNull View view, @NonNull Bundle savedInstanceState) {
+ Timber.d("onViewCreated.ContentInternalFragment");
+
+ if (this.quotationsPreferences.getDatabaseInternal()) {
+ this.fragmentQuotationsTabDatabaseTabInternalBinding.radioButtonDatabaseInternal.setChecked(true);
+ } else {
+ this.fragmentQuotationsTabDatabaseTabInternalBinding.radioButtonDatabaseInternal.setChecked(false);
+ }
+
+ createListenerRadioInternalDatabase();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+
+ this.fragmentQuotationsTabDatabaseTabInternalBinding = null;
+ }
+
+ private void createListenerRadioInternalDatabase() {
+ final RadioButton radioButtonDatabaseInternal = this.fragmentQuotationsTabDatabaseTabInternalBinding.radioButtonDatabaseInternal;
+ radioButtonDatabaseInternal.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ useInternalDatabase();
+ });
+ }
+}
diff --git a/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentWebFragment.java b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentWebFragment.java
new file mode 100755
index 000000000..fc90098c9
--- /dev/null
+++ b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/content/tabs/ContentWebFragment.java
@@ -0,0 +1,225 @@
+package com.github.jameshnsears.quoteunquote.configure.fragment.quotations.tabs.content.tabs;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RadioButton;
+import android.widget.Toast;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.github.jameshnsears.quoteunquote.R;
+import com.github.jameshnsears.quoteunquote.configure.fragment.quotations.QuotationsPreferences;
+import com.github.jameshnsears.quoteunquote.database.DatabaseRepository;
+import com.github.jameshnsears.quoteunquote.databinding.FragmentQuotationsTabDatabaseTabWebBinding;
+import com.github.jameshnsears.quoteunquote.utils.ImportHelper;
+import com.github.jameshnsears.quoteunquote.utils.scraper.ScraperData;
+
+import timber.log.Timber;
+
+@Keep
+public class ContentWebFragment extends ContentFragment {
+ @Nullable
+ public FragmentQuotationsTabDatabaseTabWebBinding fragmentQuotationsTabDatabaseTabWebBinding;
+
+ @Nullable
+ public QuotationsPreferences quotationsPreferences;
+
+ public ContentWebFragment(int widgetId) {
+ super(widgetId);
+ }
+
+ @Override
+ @NonNull
+ public View onCreateView(
+ @NonNull LayoutInflater inflater,
+ @NonNull ViewGroup container,
+ @NonNull Bundle savedInstanceState) {
+ this.quotationsPreferences = new QuotationsPreferences(this.widgetId, this.getContext());
+
+ this.fragmentQuotationsTabDatabaseTabWebBinding = FragmentQuotationsTabDatabaseTabWebBinding.inflate(this.getLayoutInflater());
+ return this.fragmentQuotationsTabDatabaseTabWebBinding.getRoot();
+ }
+
+ @Override
+ public void onViewCreated(
+ @NonNull View view, @NonNull Bundle savedInstanceState) {
+ Timber.d("onViewCreated.ContentWebFragment");
+
+ if (this.quotationsPreferences.getDatabaseExternalWeb()) {
+ this.fragmentQuotationsTabDatabaseTabWebBinding.radioButtonDatabaseExternalWeb.setChecked(true);
+ this.fragmentQuotationsTabDatabaseTabWebBinding.radioButtonDatabaseExternalWeb.setEnabled(true);
+ } else {
+ this.fragmentQuotationsTabDatabaseTabWebBinding.radioButtonDatabaseExternalWeb.setChecked(false);
+ this.fragmentQuotationsTabDatabaseTabWebBinding.radioButtonDatabaseExternalWeb.setEnabled(false);
+ }
+
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextUrl.setText(quotationsPreferences.getDatabaseWebUrl());
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathQuotation.setText(quotationsPreferences.getDatabaseWebXpathQuotation());
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathSource.setText(quotationsPreferences.getDatabaseWebXpathSource());
+ this.fragmentQuotationsTabDatabaseTabWebBinding.switchKeepLatestResponseOnly.setChecked(
+ this.quotationsPreferences.getDatabaseWebKeepLatestOnly()
+ );
+
+ createListenerRadioWebPage();
+
+ createExternalEditTextChangeListeners();
+
+ createListenerSwitchKeepLatestReponseOnly();
+
+ createListenerButtonImportWebPage();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+
+ quotationsPreferences.setDatabaseWebUrl(
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextUrl.getText().toString()
+ );
+ quotationsPreferences.setDatabaseWebXpathQuotation(
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathQuotation.getText().toString()
+ );
+ quotationsPreferences.setDatabaseWebXpathSource(
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathSource.getText().toString()
+ );
+
+ this.fragmentQuotationsTabDatabaseTabWebBinding = null;
+ }
+
+ private void createListenerRadioWebPage() {
+ final RadioButton radioButtonDatabaseInternal = this.fragmentQuotationsTabDatabaseTabWebBinding.radioButtonDatabaseExternalWeb;
+ radioButtonDatabaseInternal.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ usingWebPage();
+ });
+
+ radioButtonDatabaseInternal.setOnClickListener(v -> {
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextUrl.clearFocus();
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextUrlLayout.clearFocus();
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathQuotation.clearFocus();
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathQuotationLayout.clearFocus();
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathSource.clearFocus();
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathSourceLayout.clearFocus();
+ });
+ }
+
+ private void usingWebPage() {
+ this.quotationsPreferences.setDatabaseInternal(false);
+ this.quotationsPreferences.setDatabaseExternalCsv(false);
+ this.quotationsPreferences.setDatabaseExternalWeb(true);
+ this.quotationsPreferences.setDatabaseExternalContent(QuotationsPreferences.DATABASE_EXTERNAL_WEB);
+ DatabaseRepository.useInternalDatabase = false;
+
+ updateQuotationsUI();
+ }
+
+ private void createExternalEditTextChangeListeners() {
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextUrl.setOnFocusChangeListener((v, hasFocus) -> {
+ if (!hasFocus) {
+ Timber.d("editTextUrl=%s",
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextUrl.getText().toString());
+ quotationsPreferences.setDatabaseWebUrl(
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextUrl.getText().toString());
+ }
+ });
+
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathQuotation.setOnFocusChangeListener((v, hasFocus) -> {
+ if (!hasFocus) {
+ Timber.d("editTextXpathQuotation=%s",
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathQuotation.getText().toString());
+ quotationsPreferences.setDatabaseWebXpathQuotation(
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathQuotation.getText().toString());
+ }
+ });
+
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathSource.setOnFocusChangeListener((v, hasFocus) -> {
+ if (!hasFocus) {
+ Timber.d("editTextXpathSource=%s",
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathSource.getText().toString());
+ quotationsPreferences.setDatabaseWebXpathSource(
+ fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathSource.getText().toString());
+ }
+ });
+ }
+
+ private String getWebUrl() {
+ String url = fragmentQuotationsTabDatabaseTabWebBinding.editTextUrl.getText().toString();
+ Timber.d("url=%s", url);
+ this.quotationsPreferences.setDatabaseWebUrl(url);
+ return url;
+ }
+
+ private String getWebXpathQuotation() {
+ String xpathQuotation = fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathQuotation.getText().toString();
+ Timber.d("xpathQuotation=%s", xpathQuotation);
+ this.quotationsPreferences.setDatabaseWebXpathQuotation(xpathQuotation);
+ return xpathQuotation;
+ }
+
+ private String getWebXpathSource() {
+ String xpathSource = fragmentQuotationsTabDatabaseTabWebBinding.editTextXpathSource.getText().toString();
+ Timber.d("xpathSource=%s", xpathSource);
+ this.quotationsPreferences.setDatabaseWebXpathSource(xpathSource);
+ return xpathSource;
+ }
+
+ private void createListenerSwitchKeepLatestReponseOnly() {
+ fragmentQuotationsTabDatabaseTabWebBinding.switchKeepLatestResponseOnly.setOnCheckedChangeListener((buttonView, isChecked) ->
+ this.quotationsPreferences.setDatabaseWebKeepLatestOnly(isChecked)
+ );
+ }
+
+ private void createListenerButtonImportWebPage() {
+ fragmentQuotationsTabDatabaseTabWebBinding.buttonImportWebPage.setOnClickListener(v -> {
+ if (fragmentQuotationsTabDatabaseTabWebBinding.buttonImportWebPage.isPressed()) {
+
+ String url = getWebUrl();
+ String xpathQuotation = getWebXpathQuotation();
+ String xpathSource = getWebXpathSource();
+
+ if ("".equals(url) || "".equals(xpathQuotation) | "".equals(xpathSource)
+ || 10 > url.length()) {
+ useInternalDatabase();
+
+ Toast.makeText(
+ getContext(),
+ getContext().getString(R.string.fragment_quotations_database_scrape_fields_error_incomplete),
+ Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(
+ this.getContext(),
+ this.getContext().getString(R.string.fragment_quotations_database_scrape_importing),
+ Toast.LENGTH_SHORT).show();
+
+ ScraperData scraperData = quoteUnquoteModel.getWebPage(
+ getContext(), url, xpathQuotation, xpathSource);
+
+ if (scraperData.getScrapeResult()) {
+ quoteUnquoteModel.insertWebPage(
+ widgetId,
+ scraperData.getQuotation(),
+ scraperData.getSource(),
+ ImportHelper.DEFAULT_DIGEST
+ );
+
+ usingWebPage();
+
+ importWasSuccessful();
+
+ this.fragmentQuotationsTabDatabaseTabWebBinding.radioButtonDatabaseExternalWeb.setEnabled(false);
+
+ Toast.makeText(
+ getContext(),
+ getContext().getString(R.string.fragment_quotations_database_scrape_test_success),
+ Toast.LENGTH_SHORT).show();
+ } else {
+ useInternalDatabase();
+ }
+ }
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/database/QuotationsDatabaseFragment.java b/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/database/QuotationsDatabaseFragment.java
deleted file mode 100755
index 85f98e4b3..000000000
--- a/app/src/main/java/com/github/jameshnsears/quoteunquote/configure/fragment/quotations/tabs/database/QuotationsDatabaseFragment.java
+++ /dev/null
@@ -1,488 +0,0 @@
-package com.github.jameshnsears.quoteunquote.configure.fragment.quotations.tabs.database;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.text.method.LinkMovementMethod;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.RadioButton;
-import android.widget.Toast;
-
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.annotation.Keep;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.github.jameshnsears.quoteunquote.QuoteUnquoteModel;
-import com.github.jameshnsears.quoteunquote.R;
-import com.github.jameshnsears.quoteunquote.configure.ConfigureActivity;
-import com.github.jameshnsears.quoteunquote.configure.fragment.FragmentCommon;
-import com.github.jameshnsears.quoteunquote.configure.fragment.quotations.QuotationsPreferences;
-import com.github.jameshnsears.quoteunquote.database.DatabaseRepository;
-import com.github.jameshnsears.quoteunquote.database.quotation.QuotationEntity;
-import com.github.jameshnsears.quoteunquote.databinding.FragmentQuotationsTabDatabaseBinding;
-import com.github.jameshnsears.quoteunquote.utils.ContentSelection;
-import com.github.jameshnsears.quoteunquote.utils.ImportHelper;
-import com.github.jameshnsears.quoteunquote.utils.scraper.ScraperData;
-import com.google.android.material.snackbar.Snackbar;
-
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.LinkedHashSet;
-
-import timber.log.Timber;
-
-@Keep
-public class QuotationsDatabaseFragment extends FragmentCommon {
- @Nullable
- public FragmentQuotationsTabDatabaseBinding fragmentQuotationsTabDatabaseBinding;
-
- @Nullable
- public QuoteUnquoteModel quoteUnquoteModel;
-
- @Nullable
- public QuotationsPreferences quotationsPreferences;
-
- @Nullable
- private ActivityResultLauncher storageAccessFrameworkActivityResultCSV;
-
- public QuotationsDatabaseFragment() {
- // dark mode support
- }
-
- public QuotationsDatabaseFragment(int widgetId) {
- super(widgetId);
- }
-
- @NonNull
- public static QuotationsDatabaseFragment newInstance(
- int widgetId) {
- QuotationsDatabaseFragment fragment = new QuotationsDatabaseFragment(widgetId);
- fragment.setArguments(null);
- return fragment;
- }
-
- @Override
- public void onCreate(@NonNull Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- quoteUnquoteModel = new QuoteUnquoteModel(widgetId, getContext());
- }
-
- @Override
- @NonNull
- public View onCreateView(
- @NonNull LayoutInflater inflater,
- @NonNull ViewGroup container,
- @NonNull Bundle savedInstanceState) {
- this.quotationsPreferences = new QuotationsPreferences(this.widgetId, this.getContext());
-
- this.fragmentQuotationsTabDatabaseBinding = FragmentQuotationsTabDatabaseBinding.inflate(this.getLayoutInflater());
- return this.fragmentQuotationsTabDatabaseBinding.getRoot();
- }
-
- @Override
- public void onViewCreated(
- @NonNull View view, @NonNull Bundle savedInstanceState) {
- this.fragmentQuotationsTabDatabaseBinding.textViewExamples1.setMovementMethod(LinkMovementMethod.getInstance());
- this.fragmentQuotationsTabDatabaseBinding.textViewExamples2.setMovementMethod(LinkMovementMethod.getInstance());
- this.fragmentQuotationsTabDatabaseBinding.textViewExamples3.setMovementMethod(LinkMovementMethod.getInstance());
- this.fragmentQuotationsTabDatabaseBinding.textViewExamplesInfo.setMovementMethod(LinkMovementMethod.getInstance());
-
- this.setDatabase();
-
- this.createListenerRadioInternal();
-
- this.createListenerRadioExternalCsv();
- this.createListenerButtonImportCsv();
-
- this.createListenerRadioExternalWeb();
- this.createListenerSwitchKeepLatestReponseOnly();
- this.createListenerButtonImportWebPage();
-
- this.setHandleImportCsv();
-
- createExternalEditTextChangeListeners();
-
-// if (BuildConfig.DEBUG) {
-// String url = "https://www.bible.com/verse-of-the-day";
-// if (BuildConfig.DATABASE_QUOTATIONS.contains(".db.prod")) {
-// // javalin - Listening on http://localhost:7070/
-// url = "http://10.0.2.2:7070/verse-of-the-day";
-// }
-// fragmentQuotationsTabDatabaseBinding.editTextUrl.setText(url);
-// fragmentQuotationsTabDatabaseBinding.editTextXpathQuotation
-// .setText(getContext().getString(R.string.fragment_quotations_database_scrape_quotation_example));
-// fragmentQuotationsTabDatabaseBinding.editTextXpathSource.setText(
-// getContext().getString(R.string.fragment_quotations_database_scrape_source_example));
-// }
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
-
- quotationsPreferences.setDatabaseWebUrl(
- fragmentQuotationsTabDatabaseBinding.editTextUrl.getText().toString()
- );
- quotationsPreferences.setDatabaseWebXpathQuotation(
- fragmentQuotationsTabDatabaseBinding.editTextXpathQuotation.getText().toString()
- );
- quotationsPreferences.setDatabaseWebXpathSource(
- fragmentQuotationsTabDatabaseBinding.editTextXpathSource.getText().toString()
- );
-
- this.fragmentQuotationsTabDatabaseBinding = null;
- }
-
- private void createExternalEditTextChangeListeners() {
- fragmentQuotationsTabDatabaseBinding.editTextUrl.setOnFocusChangeListener((v, hasFocus) -> {
- if (!hasFocus) {
- Timber.d("editTextUrl=%s",
- fragmentQuotationsTabDatabaseBinding.editTextUrl.getText().toString());
- quotationsPreferences.setDatabaseWebUrl(
- fragmentQuotationsTabDatabaseBinding.editTextUrl.getText().toString());
- }
- });
-
- fragmentQuotationsTabDatabaseBinding.editTextXpathQuotation.setOnFocusChangeListener((v, hasFocus) -> {
- if (!hasFocus) {
- Timber.d("editTextXpathQuotation=%s",
- fragmentQuotationsTabDatabaseBinding.editTextXpathQuotation.getText().toString());
- quotationsPreferences.setDatabaseWebXpathQuotation(
- fragmentQuotationsTabDatabaseBinding.editTextXpathQuotation.getText().toString());
- }
- });
-
- fragmentQuotationsTabDatabaseBinding.editTextXpathSource.setOnFocusChangeListener((v, hasFocus) -> {
- if (!hasFocus) {
- Timber.d("editTextXpathSource=%s",
- fragmentQuotationsTabDatabaseBinding.editTextXpathSource.getText().toString());
- quotationsPreferences.setDatabaseWebXpathSource(
- fragmentQuotationsTabDatabaseBinding.editTextXpathSource.getText().toString());
- }
- });
- }
-
- private void setDatabase() {
- if (this.quotationsPreferences.getDatabaseInternal()) {
- setDatabaseInternal();
-
- String databseExternalContent = quotationsPreferences.getDatabaseExternalContent();
- if (databseExternalContent.equals(QuotationsPreferences.DATABASE_EXTERNAL)) {
- this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalCsv.setEnabled(true);
- }
-
- if (databseExternalContent.equals(QuotationsPreferences.DATABASE_EXTERNAL_WEB)) {
- this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalWeb.setEnabled(true);
- }
- }
-
- if (this.quotationsPreferences.getDatabaseExternalCsv()) {
- setDatabaseExternalCsv();
- }
-
- if (this.quotationsPreferences.getDatabaseExternalWeb()) {
- setDatabaseExternalWeb();
- }
-
- this.fragmentQuotationsTabDatabaseBinding.switchKeepLatestResponseOnly.setChecked(
- this.quotationsPreferences.getDatabaseWebKeepLatestOnly()
- );
-
- fragmentQuotationsTabDatabaseBinding.editTextUrl.setText(quotationsPreferences.getDatabaseWebUrl());
- fragmentQuotationsTabDatabaseBinding.editTextXpathQuotation.setText(quotationsPreferences.getDatabaseWebXpathQuotation());
- fragmentQuotationsTabDatabaseBinding.editTextXpathSource.setText(quotationsPreferences.getDatabaseWebXpathSource());
- }
-
- private void setDatabaseInternal() {
- this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseInternal.setChecked(true);
- this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalCsv.setChecked(false);
- this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalWeb.setChecked(false);
-
- this.quotationsPreferences.setDatabaseInternal(true);
- this.quotationsPreferences.setDatabaseExternalCsv(false);
- this.quotationsPreferences.setDatabaseExternalWeb(false);
-
- DatabaseRepository.useInternalDatabase = true;
- }
-
- private void setDatabaseExternalCsv() {
- this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseInternal.setChecked(false);
- this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalCsv.setChecked(true);
- this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalCsv.setEnabled(true);
- this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalWeb.setChecked(false);
-
- this.quotationsPreferences.setDatabaseInternal(false);
- this.quotationsPreferences.setDatabaseExternalCsv(true);
- this.quotationsPreferences.setDatabaseExternalWeb(false);
- this.quotationsPreferences.setDatabaseExternalContent(QuotationsPreferences.DATABASE_EXTERNAL);
-
- DatabaseRepository.useInternalDatabase = false;
- }
-
- private void setDatabaseExternalWeb() {
- this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseInternal.setChecked(false);
- this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalCsv.setChecked(false);
- this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalWeb.setChecked(true);
- this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalWeb.setEnabled(true);
-
- this.quotationsPreferences.setDatabaseInternal(false);
- this.quotationsPreferences.setDatabaseExternalCsv(false);
- this.quotationsPreferences.setDatabaseExternalWeb(true);
- this.quotationsPreferences.setDatabaseExternalContent(QuotationsPreferences.DATABASE_EXTERNAL_WEB);
-
- fragmentQuotationsTabDatabaseBinding.editTextUrl.setText(quotationsPreferences.getDatabaseWebUrl());
- fragmentQuotationsTabDatabaseBinding.editTextXpathQuotation.setText(quotationsPreferences.getDatabaseWebXpathQuotation());
- fragmentQuotationsTabDatabaseBinding.editTextXpathSource.setText(quotationsPreferences.getDatabaseWebXpathSource());
-
- DatabaseRepository.useInternalDatabase = false;
- }
-
- protected void createListenerButtonImportCsv() {
- // adb push app/src/androidTest/assets/Favourites.csv /sdcard/Download
-
- // invoke Storage Access Framework
- fragmentQuotationsTabDatabaseBinding.buttonImport.setOnClickListener(v -> {
- if (fragmentQuotationsTabDatabaseBinding.buttonImport.isEnabled()) {
- ConfigureActivity.launcherInvoked = true;
-
- Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("text/comma-separated-values");
- this.storageAccessFrameworkActivityResultCSV.launch(intent);
- }
- });
- }
-
- private void createListenerRadioInternal() {
- final RadioButton radioButtonDatabaseInternal = this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseInternal;
- radioButtonDatabaseInternal.setOnCheckedChangeListener((buttonView, isChecked) -> {
- if (isChecked) {
- setDatabaseInternal();
- updateQuotationsUI();
- }
- });
-
- radioButtonDatabaseInternal.setOnClickListener(v -> {
- fragmentQuotationsTabDatabaseBinding.editTextUrl.clearFocus();
- fragmentQuotationsTabDatabaseBinding.editTextUrlLayout.clearFocus();
- fragmentQuotationsTabDatabaseBinding.editTextXpathQuotation.clearFocus();
- fragmentQuotationsTabDatabaseBinding.editTextXpathQuotationLayout.clearFocus();
- fragmentQuotationsTabDatabaseBinding.editTextXpathSource.clearFocus();
- fragmentQuotationsTabDatabaseBinding.editTextXpathSourceLayout.clearFocus();
- });
- }
-
- private void createListenerRadioExternalCsv() {
- final RadioButton radioButtonDatabaseExternalCsv = this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalCsv;
- radioButtonDatabaseExternalCsv.setOnCheckedChangeListener((buttonView, isChecked) -> {
- if (isChecked) {
- setDatabaseExternalCsv();
- updateQuotationsUI();
- }
- });
- }
-
- private void createListenerRadioExternalWeb() {
- final RadioButton radioButtonDatabaseExternalWeb = this.fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalWeb;
- radioButtonDatabaseExternalWeb.setOnCheckedChangeListener((buttonView, isChecked) -> {
- if (isChecked) {
- setDatabaseExternalWeb();
- updateQuotationsUI();
- }
- });
- }
-
- private String getWebUrl() {
- String url = fragmentQuotationsTabDatabaseBinding.editTextUrl.getText().toString();
- Timber.d("url=%s", url);
- this.quotationsPreferences.setDatabaseWebUrl(url);
- return url;
- }
-
- private String getWebXpathQuotation() {
- String xpathQuotation = fragmentQuotationsTabDatabaseBinding.editTextXpathQuotation.getText().toString();
- Timber.d("xpathQuotation=%s", xpathQuotation);
- this.quotationsPreferences.setDatabaseWebXpathQuotation(xpathQuotation);
- return xpathQuotation;
- }
-
- private String getWebXpathSource() {
- String xpathSource = fragmentQuotationsTabDatabaseBinding.editTextXpathSource.getText().toString();
- Timber.d("xpathSource=%s", xpathSource);
- this.quotationsPreferences.setDatabaseWebXpathSource(xpathSource);
- return xpathSource;
- }
-
-
- private void createListenerSwitchKeepLatestReponseOnly() {
- fragmentQuotationsTabDatabaseBinding.switchKeepLatestResponseOnly.setOnCheckedChangeListener((buttonView, isChecked) ->
- this.quotationsPreferences.setDatabaseWebKeepLatestOnly(isChecked)
- );
- }
-
- private void createListenerButtonImportWebPage() {
- fragmentQuotationsTabDatabaseBinding.buttonImportWebPage.setOnClickListener(v -> {
- if (fragmentQuotationsTabDatabaseBinding.buttonImportWebPage.isPressed()) {
- if (fragmentQuotationsTabDatabaseBinding.buttonImportWebPage.isEnabled()) {
-
- String url = getWebUrl();
- String xpathQuotation = getWebXpathQuotation();
- String xpathSource = getWebXpathSource();
-
- if (url.equals("") || xpathQuotation.equals("") | xpathSource.equals("")
- || url.length() < 10) {
- Toast.makeText(
- getContext(),
- getContext().getString(R.string.fragment_quotations_database_scrape_fields_error_incomplete),
- Toast.LENGTH_SHORT).show();
- } else {
- Toast.makeText(
- this.getContext(),
- this.getContext().getString(R.string.fragment_quotations_database_scrape_importing),
- Toast.LENGTH_SHORT).show();
-
- ScraperData scraperData = quoteUnquoteModel.getWebPage(
- getContext(), url, xpathQuotation, xpathSource);
-
- if (scraperData.getScrapeResult()) {
- quoteUnquoteModel.insertWebPage(
- widgetId,
- scraperData.getQuotation(),
- scraperData.getSource(),
- ImportHelper.DEFAULT_DIGEST
- );
-
- fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalCsv.setEnabled(false);
- fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalCsv.setChecked(false);
-
- fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalWeb.setEnabled(true);
- fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalWeb.setChecked(true);
-
- importWasSuccessful();
-
- Toast.makeText(
- getContext(),
- getContext().getString(R.string.fragment_quotations_database_scrape_test_success),
- Toast.LENGTH_SHORT).show();
- }
- }
- }
- }
- });
- }
-
- private void importWasSuccessful() {
- quotationsPreferences.setContentSelectionSearchCount(0);
- quotationsPreferences.setContentSelectionSearch("");
-
- DatabaseRepository.useInternalDatabase = false;
-
- updateQuotationsUI();
- }
-
- private void setHandleImportCsv() {
- // default: /storage/emulated/0/Download/
- this.storageAccessFrameworkActivityResultCSV = this.registerForActivityResult(
- new ActivityResultContracts.StartActivityForResult(),
- activityResult -> {
- Timber.d("%d", activityResult.getResultCode());
-
- Toast toast = Toast.makeText(
- this.getContext(),
- this.getContext().getString(R.string.fragment_quotations_database_import_importing),
- Toast.LENGTH_SHORT);
-
- if (activityResult.getResultCode() == Activity.RESULT_OK) {
- toast.show();
-
- ParcelFileDescriptor parcelFileDescriptor = null;
- FileInputStream fileInputStream = null;
-
- try {
- parcelFileDescriptor = this.getContext().getContentResolver().openFileDescriptor(
- activityResult.getData().getData(), "r");
- fileInputStream
- = new FileInputStream(parcelFileDescriptor.getFileDescriptor());
-
- final ImportHelper importHelper = new ImportHelper();
- final LinkedHashSet quotations = importHelper.csvImportDatabase(fileInputStream);
- quoteUnquoteModel.insertQuotationsExternal(quotations);
-
- fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalCsv.setEnabled(true);
- fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalCsv.setChecked(true);
-
- fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalWeb.setEnabled(false);
- fragmentQuotationsTabDatabaseBinding.radioButtonDatabaseExternalWeb.setChecked(false);
-
- importWasSuccessful();
-
- toast.cancel();
- Toast.makeText(
- this.getContext(),
- this.getContext().getString(R.string.fragment_quotations_database_import_success),
- Toast.LENGTH_SHORT).show();
-
- } catch (final ImportHelper.ImportHelperException e) {
- toast.cancel();
-
- String message = "";
- if (e.lineNumber == -1) {
- message = this.getContext().getString(
- R.string.fragment_quotations_database_import_contents_0,
- e.getMessage()
- );
-
- } else {
- message = this.getContext().getString(
- R.string.fragment_quotations_database_import_contents_1,
- e.lineNumber,
- e.getMessage()
- );
- }
-
- Snackbar.make(
- this.fragmentQuotationsTabDatabaseBinding.getRoot(),
- message,
- Snackbar.LENGTH_LONG).show();
-
- } catch (final FileNotFoundException e) {
- toast.cancel();
-
- Snackbar.make(
- this.fragmentQuotationsTabDatabaseBinding.getRoot(),
- this.getContext().getString(
- R.string.fragment_quotations_database_import_contents_0,
- e.getMessage()
- ),
- Snackbar.LENGTH_LONG).show();
-
- } finally {
- try {
- if (fileInputStream != null) {
- fileInputStream.close();
- }
- if (parcelFileDescriptor != null) {
- parcelFileDescriptor.close();
- }
- } catch (IOException e) {
- Timber.e(e.getMessage());
- }
- }
- }
-
- ConfigureActivity.launcherInvoked = false;
- });
- }
-
- private void updateQuotationsUI() {
- QuotationsPreferences quotationsPreferences = new QuotationsPreferences(widgetId, getContext());
- quotationsPreferences.setContentSelection(ContentSelection.ALL);
- }
-}
diff --git a/app/src/main/java/com/github/jameshnsears/quoteunquote/utils/ImportHelper.java b/app/src/main/java/com/github/jameshnsears/quoteunquote/utils/ImportHelper.java
index 87f5084af..78cd4ce48 100755
--- a/app/src/main/java/com/github/jameshnsears/quoteunquote/utils/ImportHelper.java
+++ b/app/src/main/java/com/github/jameshnsears/quoteunquote/utils/ImportHelper.java
@@ -130,7 +130,7 @@ public LinkedHashSet csvImportDatabase(InputStream inputStream)
private String makeDigest(int recordCount, String author, String quotation) {
String digest;
- if (recordCount == 0) {
+ if (recordCount == 1) {
digest = DEFAULT_DIGEST;
} else {
digest = makeDigest(quotation, author);
diff --git a/app/src/main/res/layout/fragment_notifications.xml b/app/src/main/res/layout/fragment_notifications.xml
index 2d998f2ec..611755f14 100755
--- a/app/src/main/res/layout/fragment_notifications.xml
+++ b/app/src/main/res/layout/fragment_notifications.xml
@@ -404,6 +404,7 @@
-
+ android:layout_height="wrap_content">
-
+ android:icon="@drawable/ic_quotations_database_internal_24" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:icon="@drawable/ic_quotations_database_external_24" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/app/src/main/res/layout/fragment_quotations_tab_database_tab_csv.xml b/app/src/main/res/layout/fragment_quotations_tab_database_tab_csv.xml
new file mode 100755
index 000000000..a32364de9
--- /dev/null
+++ b/app/src/main/res/layout/fragment_quotations_tab_database_tab_csv.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_quotations_tab_database_tab_internal.xml b/app/src/main/res/layout/fragment_quotations_tab_database_tab_internal.xml
new file mode 100755
index 000000000..7918b45ff
--- /dev/null
+++ b/app/src/main/res/layout/fragment_quotations_tab_database_tab_internal.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_quotations_tab_database_tab_web.xml b/app/src/main/res/layout/fragment_quotations_tab_database_tab_web.xml
new file mode 100755
index 000000000..01452b180
--- /dev/null
+++ b/app/src/main/res/layout/fragment_quotations_tab_database_tab_web.xml
@@ -0,0 +1,237 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 88d9e6cb3..04ad82d89 100755
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -71,12 +71,12 @@
Tap: Share; Long tap: toggle Favourite
Force enable Browse & Export
- Database
- Internal
+ Content
+ Internal Database
Discover high quality Quotations!
External
- .csv file
+ CSV file
Import
\u2022 Expected format is Source||Quotation per line
@@ -227,7 +227,7 @@
Last backup: %s
\u2022 The Alarm & reminders permission needs to be granted for this feature
\u2022 Each time you Favorite / Unfavourite
- \u2022 Backup takes place after approximately 1 minute of app widget inactivity
+ \u2022 Backup takes place after approximately 1 minute of app widget inactivity, with unlimitied retries
Quotation
Source
diff --git a/build.gradle b/build.gradle
index 6813b4c5d..15481edbc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -17,6 +17,7 @@ buildscript {
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20'
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:5.1.0.4882'
classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.25.0'
+ classpath "org.jetbrains.kotlin:compose-compiler-gradle-plugin:2.0.20"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml
index dcd54f159..f89c73aff 100644
--- a/config/detekt/baseline.xml
+++ b/config/detekt/baseline.xml
@@ -2,18 +2,24 @@
+ FunctionNaming:ContentCsvComposable.kt$@Composable fun Greeting( stateFlow: StateFlow<Boolean>, name: String, )
+ FunctionNaming:ContentCsvComposable.kt$@Preview(apiLevel = 34) @Composable fun PreviewGreeting()
+ LongMethod:ContentCsvFragment.kt$ContentCsvFragment$private fun setHandleImportCsv()
MagicNumber:AutoCloudBackup.kt$AutoCloudBackup$60_000L
MagicNumber:Scraper.kt$Scraper$1000
MaxLineLength:ScraperTest.kt$ScraperTest$"But the God of all grace, who hath called us unto his eternal glory by Christ Jesus, after that ye have suffered a while, make you perfect, stablish, strengthen, settle"
NestedBlockDepth:TransferRestore.kt$TransferRestore$private fun restoreCurrent( context: Context, databaseRepository: DatabaseRepository, currentList: List<Current>, )
NestedBlockDepth:TransferRestore.kt$TransferRestore$private fun restorePrevious( context: Context, databaseRepository: DatabaseRepository, previousList: List<Previous>, )
+ ProtectedMemberInFinalClass:ContentCsvFragment.kt$ContentCsvFragment$protected fun createListenerButtonImportCsv()
SwallowedException:Scraper.kt$Scraper$e: Exception
SwallowedException:Scraper.kt$Scraper$e: ScraperXpathException
ThrowsCount:Scraper.kt$Scraper$private fun getXpath(document: Document, xpath: String): String
TooGenericExceptionCaught:Scraper.kt$Scraper$e: Exception
TooManyFunctions:TransferRestore.kt$TransferRestore : TransferCommon
+ UnusedPrivateProperty:ContentCsvComposable.kt$val state = stateFlow.collectAsState()
UnusedPrivateProperty:DatabaseQuotationsRandomTest.kt$DatabaseQuotationsRandomTest$i
UtilityClassWithPublicConstructor:ListViewLayoutIdHelper.kt$ListViewLayoutIdHelper
UtilityClassWithPublicConstructor:SyncJsonSchemaValidation.kt$SyncJsonSchemaValidation
+ VariableNaming:ContentCsvComposable.kt$val _stateFlow = MutableStateFlow(true)
diff --git a/config/lint/baseline.xml b/config/lint/baseline.xml
index 63fa1fd82..00010bf2a 100644
--- a/config/lint/baseline.xml
+++ b/config/lint/baseline.xml
@@ -6,216 +6,212 @@
errorLine2=" ~~~~~~~~~~~"
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`.">
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+ file="src/main/java/com/github/jameshnsears/quoteunquote/QuoteUnquoteModel.java"
+ line="846" />
@@ -274,77 +270,68 @@
-
-
+
-
@@ -358,22 +345,19 @@
@@ -406,22 +390,21 @@
-
+
-
+
-
+
@@ -435,28 +418,25 @@
errorLine2=" ~~~~"
id="WeekBasedYear"
message="`DateFormat` character 'Y' in YYYY is the week-era-year; did you mean 'y'?">
-
+
diff --git a/fastlane/metadata/android/en-US/changelogs/174.txt b/fastlane/metadata/android/en-US/changelogs/174.txt
deleted file mode 100644
index d4be674f5..000000000
--- a/fastlane/metadata/android/en-US/changelogs/174.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-• Rename notification channel
-• Add retry to Auto cloud backup
-• Simplified date format in Auto cloud backup
diff --git a/fastlane/metadata/android/en-US/changelogs/177.txt b/fastlane/metadata/android/en-US/changelogs/177.txt
new file mode 100644
index 000000000..1b0d04677
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/177.txt
@@ -0,0 +1,3 @@
+• Fix CSV import regression
+• Rename & de-clutter Database tab in configuration
+• Bump dependencies