forked from Louvorg/ReaderForSelfoss-multiplatform
		
	Compare commits
	
		
			24 Commits
		
	
	
		
			v122123602
			...
			v123010041
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e0c118a73e | ||
|  | 4e61b2aed6 | ||
|  | ba2758c0a3 | ||
|  | c718b966a1 | ||
|  | 99438e142f | ||
|  | 4d8076c3cf | ||
|  | db75c5b74a | ||
|  | 966a082147 | ||
|  | cd20a5ec29 | ||
|  | cc4c1c9201 | ||
|  | ff021d572c | ||
|  | 89992967be | ||
|  | 3c68bde62b | ||
|  | c38251f5b3 | ||
|  | a01f6d2322 | ||
|  | 417a33eb25 | ||
|  | 2e7f7f23b3 | ||
|  | e5e182761e | ||
|  | a094d88799 | ||
| e51915d1cd | |||
| 3a654f6ede | |||
| 5227751dca | |||
|  | 27eafe4ff4 | ||
|  | 8c83a9408b | 
							
								
								
									
										15
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								.drone.yml
									
									
									
									
									
								
							| @@ -35,7 +35,6 @@ steps: | ||||
| trigger: | ||||
|   event: | ||||
|     - push | ||||
|     - pull_request | ||||
|  | ||||
| --- | ||||
| kind: pipeline | ||||
| @@ -43,12 +42,19 @@ type: docker | ||||
| name: Publish | ||||
|  | ||||
| steps: | ||||
|   - name: createTag | ||||
|   - name: createTagAndChangelog | ||||
|     image: ubuntu:latest | ||||
|     commands: | ||||
|       - apt-get update && apt-get install -y git | ||||
|       - git fetch --tags -p | ||||
|       - VER=$(git describe --tags --abbrev=0) | ||||
|       - CHANGELOG=$(git log $VER..HEAD --pretty="- %s") | ||||
|       - echo "**$VER**\n\n$CHANGELOG\n\n--------------------------------------------------------------------\n\n$(cat CHANGELOG.md)" > CHANGELOG.md | ||||
|       - git add CHANGELOG.md | ||||
|       - git commit -m "Changelog for $VER [CI SKIP]" | ||||
|       - ./build.sh --publish --from-ci | ||||
|       - git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git | ||||
|       - git push pushing master | ||||
|       - git push pushing --tags | ||||
|     environment: | ||||
|       TZ: Europe/Paris | ||||
| @@ -77,10 +83,7 @@ steps: | ||||
|         from_secret: privateKey | ||||
|       command_timeout: 2m | ||||
|       script: | ||||
|         - cd /home/ubuntu | ||||
|         - sudo rm -rf /var/www/amine/version.txt | ||||
|         - sudo chown www-data:www-data ./version.txt | ||||
|         - sudo mv version.txt /var/www/amine/ | ||||
|         - cd /home/ubuntu && sudo rm -rf /var/www/amine/version.txt && sudo chown www-data:www-data ./version.txt && sudo mv version.txt /var/www/amine/ | ||||
|  | ||||
| trigger: | ||||
|   event: | ||||
|   | ||||
							
								
								
									
										27
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,30 @@ | ||||
| **v122123641** | ||||
|  | ||||
| - feat: Disable the failing source in the filter sheet. | ||||
| - feat: Display the source error in the sources list. | ||||
|  | ||||
| -------------------------------------------------------------------- | ||||
|  | ||||
| **v122123631** | ||||
|  | ||||
| - build: Added back maven repos (see https://gitlab.com/fdroid/fdroiddata/-/commit/1fb9d60dc58511abc2bb4eb321977922a0682c8b#note_1223925153) | ||||
| - build: Added back maven repos (see https://gitlab.com/fdroid/fdroiddata/-/commit/1fb9d60dc58511abc2bb4eb321977922a0682c8b#note_1223925153) | ||||
| - debug: trying to resolve `Canvas: trying to use a recycled bitmap`. | ||||
| - fix: NPE may be caused by the binding or the title that was null. | ||||
| - chore: Skip drone pipeline on changelog push. | ||||
|  | ||||
| -------------------------------------------------------------------- | ||||
|  | ||||
| **v122123621** | ||||
|  | ||||
| - fix: Automatic CHANGELOG generation. | ||||
| - Merge pull request 'Sources Upsert' (#119) from sources-edit into master | ||||
| - Source update screen. | ||||
| - Sources menu. | ||||
| - chore: Automatic CHANGELOG generation. | ||||
|  | ||||
| -------------------------------------------------------------------- | ||||
|  | ||||
| # V2/Multiplatform rewrite | ||||
|  | ||||
| **v1** | ||||
|   | ||||
| @@ -52,7 +52,7 @@ | ||||
|                 android:value=".HomeActivity" /> | ||||
|         </activity> | ||||
|         <activity | ||||
|             android:name=".AddSourceActivity" | ||||
|             android:name=".UpsertSourceActivity" | ||||
|             android:parentActivityName=".SourcesActivity" | ||||
|             android:exported="true"> | ||||
|             <meta-data | ||||
|   | ||||
| @@ -603,6 +603,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar | ||||
|                 settingsLauncher.launch(Intent(this, SettingsActivity::class.java)) | ||||
|                 return true | ||||
|             } | ||||
|             R.id.action_sources -> { | ||||
|                 startActivity(Intent(this, SourcesActivity::class.java)) | ||||
|                 return true | ||||
|             } | ||||
|             else -> return super.onOptionsItemSelected(item) | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -3,10 +3,7 @@ package bou.amine.apps.readerforselfossv2.android | ||||
| import android.app.NotificationChannel | ||||
| import android.app.NotificationManager | ||||
| import android.content.Context | ||||
| import android.graphics.drawable.Drawable | ||||
| import android.net.Uri | ||||
| import android.os.Build | ||||
| import android.widget.ImageView | ||||
| import android.widget.Toast | ||||
| import androidx.lifecycle.DefaultLifecycleObserver | ||||
| import androidx.lifecycle.LifecycleOwner | ||||
| @@ -18,8 +15,6 @@ import bou.amine.apps.readerforselfossv2.dao.DriverFactory | ||||
| import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB | ||||
| import bou.amine.apps.readerforselfossv2.repository.Repository | ||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||
| import com.bumptech.glide.Glide | ||||
| import com.bumptech.glide.request.RequestOptions | ||||
| import com.github.ln_12.library.ConnectivityStatus | ||||
| import io.github.aakira.napier.DebugAntilog | ||||
| import io.github.aakira.napier.Napier | ||||
| @@ -83,6 +78,8 @@ class MyApp : MultiDexApplication(), DIAware { | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         repository.migrate(driverFactory) | ||||
|     } | ||||
|  | ||||
|     override fun attachBaseContext(base: Context?) { | ||||
|   | ||||
| @@ -73,7 +73,7 @@ class SourcesActivity : AppCompatActivity(), DIAware { | ||||
|         } | ||||
|  | ||||
|         binding.fab.setOnClickListener { | ||||
|             startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java)) | ||||
|             startActivity(Intent(this@SourcesActivity, UpsertSourceActivity::class.java)) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,12 +3,15 @@ package bou.amine.apps.readerforselfossv2.android | ||||
| import android.content.Intent | ||||
| import android.os.Bundle | ||||
| import android.view.View | ||||
| import android.widget.* | ||||
| import android.widget.AdapterView | ||||
| import android.widget.ArrayAdapter | ||||
| import android.widget.TextView | ||||
| import android.widget.Toast | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import androidx.constraintlayout.widget.ConstraintLayout | ||||
| import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding | ||||
| import bou.amine.apps.readerforselfossv2.android.databinding.ActivityUpsertSourceBinding | ||||
| import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid | ||||
| import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException | ||||
| import bou.amine.apps.readerforselfossv2.model.SelfossModel | ||||
| import bou.amine.apps.readerforselfossv2.repository.Repository | ||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| @@ -19,38 +22,59 @@ import org.kodein.di.android.closestDI | ||||
| import org.kodein.di.instance | ||||
| 
 | ||||
| 
 | ||||
| class AddSourceActivity : AppCompatActivity(), DIAware { | ||||
| class UpsertSourceActivity : AppCompatActivity(), DIAware { | ||||
| 
 | ||||
|     private var existingSource: SelfossModel.Source? = null | ||||
|     private var mSpoutsValue: String? = null | ||||
| 
 | ||||
|     private lateinit var binding: ActivityAddSourceBinding | ||||
|     private lateinit var binding: ActivityUpsertSourceBinding | ||||
| 
 | ||||
|     override val di by closestDI() | ||||
|     private val repository : Repository by instance() | ||||
|     private val appSettingsService : AppSettingsService by instance() | ||||
|     private val repository: Repository by instance() | ||||
|     private val appSettingsService: AppSettingsService by instance() | ||||
| 
 | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         binding = ActivityAddSourceBinding.inflate(layoutInflater) | ||||
|         binding = ActivityUpsertSourceBinding.inflate(layoutInflater) | ||||
|         val view = binding.root | ||||
| 
 | ||||
|         existingSource = repository.getSelectedSource() | ||||
|         if (existingSource != null) { | ||||
|             binding.formContainer.visibility = View.GONE | ||||
|             binding.progress.visibility = View.VISIBLE | ||||
|         } | ||||
|         val title = if (existingSource == null) R.string.add_source else R.string.update_source | ||||
| 
 | ||||
|         supportFragmentManager.addOnBackStackChangedListener { | ||||
|             if (supportFragmentManager.backStackEntryCount == 0) { | ||||
|                 setTitle(title) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         setContentView(view) | ||||
| 
 | ||||
|         setSupportActionBar(binding.toolbar) | ||||
|         supportActionBar?.setDisplayHomeAsUpEnabled(true) | ||||
|         supportActionBar?.setDisplayShowHomeEnabled(true) | ||||
|         supportActionBar?.title = resources.getString(title) | ||||
| 
 | ||||
|         maybeGetDetailsFromIntentSharing(intent, binding.sourceUri, binding.nameInput) | ||||
| 
 | ||||
|         maybeGetDetailsFromIntentSharing(intent) | ||||
| 
 | ||||
|         binding.saveBtn.setOnClickListener { | ||||
|             handleSaveSource( | ||||
|                 binding.tags, | ||||
|                 binding.nameInput.text.toString(), | ||||
|                 binding.sourceUri.text.toString() | ||||
|             ) | ||||
|             handleSaveSource() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun initFields(items: Map<String, SelfossModel.Spout>) { | ||||
|         binding.nameInput.setText(existingSource!!.title) | ||||
|         binding.tags.setText(existingSource!!.tags.joinToString(", ")) | ||||
|         binding.sourceUri.setText(existingSource!!.params?.url) | ||||
|         binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout)) | ||||
|         binding.progress.visibility = View.GONE | ||||
|         binding.formContainer.visibility = View.VISIBLE | ||||
|     } | ||||
| 
 | ||||
|     override fun onResume() { | ||||
|         super.onResume() | ||||
| 
 | ||||
| @@ -58,17 +82,13 @@ class AddSourceActivity : AppCompatActivity(), DIAware { | ||||
|         if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid()) { | ||||
|             mustLoginToAddSource() | ||||
|         } else { | ||||
|             handleSpoutsSpinner(binding.spoutsSpinner, binding.progress, binding.formContainer) | ||||
|             handleSpoutsSpinner() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun handleSpoutsSpinner( | ||||
|         spoutsSpinner: Spinner, | ||||
|         mProgress: ProgressBar, | ||||
|         formContainer: ConstraintLayout | ||||
|     ) { | ||||
|     private fun handleSpoutsSpinner() { | ||||
|         val spoutsKV = HashMap<String, String>() | ||||
|         spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { | ||||
|         binding.spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { | ||||
|             override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) { | ||||
|                 if (view != null) { | ||||
|                     val spoutName = (view as TextView).text.toString() | ||||
| @@ -84,11 +104,11 @@ class AddSourceActivity : AppCompatActivity(), DIAware { | ||||
| 
 | ||||
|         fun handleSpoutFailure(networkIssue: Boolean = false) { | ||||
|             Toast.makeText( | ||||
|                 this@AddSourceActivity, | ||||
|                 this@UpsertSourceActivity, | ||||
|                 if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts, | ||||
|                 Toast.LENGTH_SHORT | ||||
|             ).show() | ||||
|             mProgress.visibility = View.GONE | ||||
|             binding.progress.visibility = View.GONE | ||||
|         } | ||||
| 
 | ||||
|         CoroutineScope(Dispatchers.Main).launch { | ||||
| @@ -100,17 +120,21 @@ class AddSourceActivity : AppCompatActivity(), DIAware { | ||||
|                         spoutsKV[value.name] = key | ||||
|                     } | ||||
| 
 | ||||
|                     mProgress.visibility = View.GONE | ||||
|                     formContainer.visibility = View.VISIBLE | ||||
|                     binding.progress.visibility = View.GONE | ||||
|                     binding.formContainer.visibility = View.VISIBLE | ||||
| 
 | ||||
|                     val spinnerArrayAdapter = | ||||
|                         ArrayAdapter( | ||||
|                             this@AddSourceActivity, | ||||
|                             this@UpsertSourceActivity, | ||||
|                             android.R.layout.simple_spinner_item, | ||||
|                             itemsStrings | ||||
|                         ) | ||||
|                     spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) | ||||
|                     spoutsSpinner.adapter = spinnerArrayAdapter | ||||
|                     binding.spoutsSpinner.adapter = spinnerArrayAdapter | ||||
| 
 | ||||
|                     if (existingSource != null) { | ||||
|                         initFields(items) | ||||
|                     } | ||||
|                 } else { | ||||
|                     handleSpoutFailure() | ||||
|                 } | ||||
| @@ -121,13 +145,11 @@ class AddSourceActivity : AppCompatActivity(), DIAware { | ||||
|     } | ||||
| 
 | ||||
|     private fun maybeGetDetailsFromIntentSharing( | ||||
|         intent: Intent, | ||||
|         sourceUri: EditText, | ||||
|         nameInput: EditText | ||||
|         intent: Intent | ||||
|     ) { | ||||
|         if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) { | ||||
|             sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT)) | ||||
|             nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE)) | ||||
|             binding.sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT)) | ||||
|             binding.nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE)) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @@ -138,7 +160,8 @@ class AddSourceActivity : AppCompatActivity(), DIAware { | ||||
|         finish() | ||||
|     } | ||||
| 
 | ||||
|     private fun handleSaveSource(tags: EditText, title: String, url: String) { | ||||
|     private fun handleSaveSource() { | ||||
|         val url = binding.sourceUri.text.toString() | ||||
| 
 | ||||
|         val sourceDetailsUnavailable = | ||||
|             title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty() | ||||
| @@ -149,18 +172,27 @@ class AddSourceActivity : AppCompatActivity(), DIAware { | ||||
|             } | ||||
|             else -> { | ||||
|                 CoroutineScope(Dispatchers.Main).launch { | ||||
|                     val successfullyAddedSource = repository.createSource( | ||||
|                         title, | ||||
|                         url, | ||||
|                         mSpoutsValue!!, | ||||
|                         tags.text.toString(), | ||||
|                         "", | ||||
|                     ) | ||||
|                     val successfullyAddedSource = if (existingSource != null) { | ||||
|                         repository.updateSource( | ||||
|                             existingSource!!.id, | ||||
|                             binding.nameInput.text.toString(), | ||||
|                             url, | ||||
|                             mSpoutsValue!!, | ||||
|                             binding.tags.text.toString() | ||||
|                         ) | ||||
|                     } else { | ||||
|                         repository.createSource( | ||||
|                             binding.nameInput.text.toString(), | ||||
|                             url, | ||||
|                             mSpoutsValue!!, | ||||
|                             binding.tags.text.toString(), | ||||
|                         ) | ||||
|                     } | ||||
|                     if (successfullyAddedSource) { | ||||
|                         finish() | ||||
|                     } else { | ||||
|                         Toast.makeText( | ||||
|                             this@AddSourceActivity, | ||||
|                             this@UpsertSourceActivity, | ||||
|                             R.string.cant_create_source, | ||||
|                             Toast.LENGTH_SHORT | ||||
|                         ).show() | ||||
| @@ -169,4 +201,9 @@ class AddSourceActivity : AppCompatActivity(), DIAware { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     override fun onDestroy() { | ||||
|         super.onDestroy() | ||||
|         repository.unsetSelectedSource() | ||||
|     } | ||||
| } | ||||
| @@ -2,12 +2,15 @@ package bou.amine.apps.readerforselfossv2.android.adapters | ||||
|  | ||||
| import android.app.Activity | ||||
| import android.content.Context | ||||
| import android.content.Intent | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import android.widget.Button | ||||
| import android.widget.Toast | ||||
| import androidx.constraintlayout.widget.ConstraintLayout | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity | ||||
| import bou.amine.apps.readerforselfossv2.android.R | ||||
| import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding | ||||
| import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString | ||||
| @@ -58,6 +61,13 @@ class SourcesListAdapter( | ||||
|             c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage) | ||||
|         } | ||||
|  | ||||
|         if (itm.error.isNotBlank()) { | ||||
|             binding.errorText.visibility = View.VISIBLE | ||||
|             binding.errorText.text = itm.error | ||||
|         } else { | ||||
|             binding.errorText.visibility = View.GONE | ||||
|         } | ||||
|  | ||||
|         binding.sourceTitle.text = itm.title.getHtmlDecoded() | ||||
|     } | ||||
|  | ||||
| @@ -78,9 +88,9 @@ class SourcesListAdapter( | ||||
|             val deleteBtn: Button = mView.findViewById(R.id.deleteBtn) | ||||
|  | ||||
|             deleteBtn.setOnClickListener { | ||||
|                 val (id) = items[bindingAdapterPosition] | ||||
|                 val (id, title) = items[bindingAdapterPosition] | ||||
|                 CoroutineScope(Dispatchers.IO).launch { | ||||
|                     val successfullyDeletedSource = repository.deleteSource(id) | ||||
|                     val successfullyDeletedSource = repository.deleteSource(id, title) | ||||
|                     if (successfullyDeletedSource) { | ||||
|                         items.removeAt(bindingAdapterPosition) | ||||
|                         notifyItemRemoved(bindingAdapterPosition) | ||||
| @@ -94,6 +104,14 @@ class SourcesListAdapter( | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             mView.setOnClickListener { | ||||
|                 val source = items[bindingAdapterPosition] | ||||
|  | ||||
|                 repository.setSelectedSource(source) | ||||
|                 app.startActivity(Intent(app, UpsertSourceActivity::class.java)) | ||||
|  | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -66,8 +66,7 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|     private lateinit var allImages : ArrayList<String> | ||||
|     private lateinit var fab: FloatingActionButton | ||||
|     private lateinit var textAlignment: String | ||||
|     private var _binding: FragmentArticleBinding? = null | ||||
|     private val binding get() = _binding | ||||
|     private lateinit var binding: FragmentArticleBinding | ||||
|  | ||||
|     override val di : DI by closestDI() | ||||
|     private val repository: Repository by instance() | ||||
| @@ -95,7 +94,7 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|         savedInstanceState: Bundle? | ||||
|     ): View { | ||||
|         try { | ||||
|             _binding = FragmentArticleBinding.inflate(inflater, container, false) | ||||
|             binding = FragmentArticleBinding.inflate(inflater, container, false) | ||||
|  | ||||
|             url = item.getLinkDecoded() | ||||
|             contentText = item.content | ||||
| @@ -110,13 +109,13 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|  | ||||
|             refreshAlignment() | ||||
|  | ||||
|             fab = binding!!.fab | ||||
|             fab = binding.fab | ||||
|  | ||||
|             fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent)) | ||||
|  | ||||
|             fab.rippleColor = resources.getColor(R.color.colorAccentDark) | ||||
|  | ||||
|             val floatingToolbar: FloatingToolbar = binding!!.floatingToolbar | ||||
|             val floatingToolbar: FloatingToolbar = binding.floatingToolbar | ||||
|             floatingToolbar.attachFab(fab) | ||||
|  | ||||
|             floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent)) | ||||
| @@ -164,35 +163,35 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|                 floatingToolbar.show() | ||||
|             } | ||||
|  | ||||
|             binding!!.source.text = contentSource | ||||
|             binding.source.text = contentSource | ||||
|             if (typeface != null) { | ||||
|                 binding!!.source.typeface = typeface | ||||
|                 binding.source.typeface = typeface | ||||
|             } | ||||
|  | ||||
|             if (contentText.isEmptyOrNullOrNullString()) { | ||||
|                 getContentFromMercury() | ||||
|             } else { | ||||
|                 binding!!.titleView.text = contentTitle | ||||
|                 binding.titleView.text = contentTitle | ||||
|                 if (typeface != null) { | ||||
|                     binding!!.titleView.typeface = typeface | ||||
|                     binding.titleView.typeface = typeface | ||||
|                 } | ||||
|  | ||||
|                 htmlToWebview() | ||||
|  | ||||
|                 if (!contentImage.isEmptyOrNullOrNullString() && context != null) { | ||||
|                     binding!!.imageView.visibility = View.VISIBLE | ||||
|                     binding.imageView.visibility = View.VISIBLE | ||||
|                     Glide | ||||
|                         .with(requireContext()) | ||||
|                         .asBitmap() | ||||
|                         .load(contentImage) | ||||
|                         .apply(RequestOptions.fitCenterTransform()) | ||||
|                         .into(binding!!.imageView) | ||||
|                         .into(binding.imageView) | ||||
|                 } else { | ||||
|                     binding!!.imageView.visibility = View.GONE | ||||
|                     binding.imageView.visibility = View.GONE | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             binding!!.nestedScrollView.setOnScrollChangeListener( | ||||
|             binding.nestedScrollView.setOnScrollChangeListener( | ||||
|                 NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY -> | ||||
|                     if (scrollY > oldScrollY) { | ||||
|                         floatingToolbar.hide() | ||||
| @@ -221,12 +220,7 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|                 .show() | ||||
|         } | ||||
|  | ||||
|         return binding!!.root | ||||
|     } | ||||
|  | ||||
|     override fun onDestroyView() { | ||||
|         super.onDestroyView() | ||||
|         _binding = null | ||||
|         return binding.root | ||||
|     } | ||||
|  | ||||
|     private fun refreshAlignment() { | ||||
| @@ -239,16 +233,16 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|  | ||||
|     private fun getContentFromMercury() { | ||||
|         if (repository.isNetworkAvailable()) { | ||||
|             binding!!.progressBar.visibility = View.VISIBLE | ||||
|             binding.progressBar.visibility = View.VISIBLE | ||||
|  | ||||
|             CoroutineScope(Dispatchers.Main).launch { | ||||
|                 try { | ||||
|                     val response = mercuryApi.query(url) | ||||
|                     if (response.success && response.data != null && !response.data?.content.isNullOrEmpty()) { | ||||
|                         binding!!.titleView.text = response.data!!.title.orEmpty() | ||||
|                         binding.titleView.text = response.data!!.title.orEmpty() | ||||
|                         try { | ||||
|                             if (typeface != null) { | ||||
|                                 binding!!.titleView.typeface = typeface | ||||
|                                 binding.titleView.typeface = typeface | ||||
|                             } | ||||
|                         } catch (e: Exception) { | ||||
|                             e.sendSilentlyWithAcraWithName("getContentFromMercury > typeface") | ||||
| @@ -272,7 +266,7 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|  | ||||
|                         if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) { | ||||
|                             try { | ||||
|                                 binding!!.imageView.visibility = View.VISIBLE | ||||
|                                 binding.imageView.visibility = View.VISIBLE | ||||
|                                 try { | ||||
|                                     Glide | ||||
|                                         .with(requireContext()) | ||||
| @@ -281,7 +275,7 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|                                             response.data!!.lead_image_url.orEmpty() | ||||
|                                         ) | ||||
|                                         .apply(RequestOptions.fitCenterTransform()) | ||||
|                                         .into(binding!!.imageView) | ||||
|                                         .into(binding.imageView) | ||||
|                                 } catch (e: Exception) { | ||||
|                                     e.sendSilentlyWithAcraWithName("getContentFromMercury > glide lead image") | ||||
|                                 } | ||||
| @@ -289,12 +283,12 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|                                 e.sendSilentlyWithAcraWithName("getContentFromMercury > outside glide lead image") | ||||
|                             } | ||||
|                         } else { | ||||
|                             binding!!.imageView.visibility = View.GONE | ||||
|                             binding.imageView.visibility = View.GONE | ||||
|                         } | ||||
|  | ||||
|                         try { | ||||
|                             binding!!.nestedScrollView.scrollTo(0, 0) | ||||
|                             binding!!.progressBar.visibility = View.GONE | ||||
|                             binding.nestedScrollView.scrollTo(0, 0) | ||||
|                             binding.progressBar.visibility = View.GONE | ||||
|                         } catch (e: Exception) { | ||||
|                             e.sendSilentlyWithAcraWithName("getContentFromMercury > scrollview") | ||||
|                         } | ||||
| @@ -317,8 +311,8 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|         val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs) | ||||
|  | ||||
|  | ||||
|         binding!!.webcontent.settings.standardFontFamily = a.getString(0) | ||||
|         binding!!.webcontent.visibility = View.VISIBLE | ||||
|         binding.webcontent.settings.standardFontFamily = a.getString(0) | ||||
|         binding.webcontent.visibility = View.VISIBLE | ||||
|  | ||||
|         val colorOnSurface = TypedValue() | ||||
|         requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true) | ||||
| @@ -326,14 +320,14 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|         val colorSurface = TypedValue() | ||||
|         requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true) | ||||
|  | ||||
|         binding!!.webcontent.settings.useWideViewPort = true | ||||
|         binding!!.webcontent.settings.loadWithOverviewMode = true | ||||
|         binding!!.webcontent.settings.javaScriptEnabled = false | ||||
|         binding.webcontent.settings.useWideViewPort = true | ||||
|         binding.webcontent.settings.loadWithOverviewMode = true | ||||
|         binding.webcontent.settings.javaScriptEnabled = false | ||||
|  | ||||
|         binding!!.webcontent.webViewClient = object : WebViewClient() { | ||||
|         binding.webcontent.webViewClient = object : WebViewClient() { | ||||
|             @Deprecated("Deprecated in Java") | ||||
|             override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean { | ||||
|                 if (binding!!.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { | ||||
|                 if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { | ||||
|                     requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) | ||||
|                 } | ||||
|                 return true | ||||
| @@ -377,9 +371,9 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|         binding!!.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)} | ||||
|         binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)} | ||||
|  | ||||
|         binding!!.webcontent.settings.layoutAlgorithm = | ||||
|         binding.webcontent.settings.layoutAlgorithm = | ||||
|                 WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING | ||||
|  | ||||
|         var baseUrl: String? = null | ||||
| @@ -410,7 +404,7 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|             "" | ||||
|         } | ||||
|  | ||||
|         binding!!.webcontent.loadDataWithBaseURL( | ||||
|         binding.webcontent.loadDataWithBaseURL( | ||||
|             baseUrl, | ||||
|             """<html> | ||||
|                 |<head> | ||||
| @@ -463,17 +457,17 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|     } | ||||
|  | ||||
|     fun scrollDown() { | ||||
|         val height = binding!!.nestedScrollView.measuredHeight | ||||
|         binding!!.nestedScrollView.smoothScrollBy(0, height/2) | ||||
|         val height = binding.nestedScrollView.measuredHeight | ||||
|         binding.nestedScrollView.smoothScrollBy(0, height/2) | ||||
|     } | ||||
|  | ||||
|     fun scrollUp() { | ||||
|         val height = binding!!.nestedScrollView.measuredHeight | ||||
|         binding!!.nestedScrollView.smoothScrollBy(0, -height/2) | ||||
|         val height = binding.nestedScrollView.measuredHeight | ||||
|         binding.nestedScrollView.smoothScrollBy(0, -height/2) | ||||
|     } | ||||
|  | ||||
|     private fun openInBrowserAfterFailing() { | ||||
|         binding!!.progressBar.visibility = View.GONE | ||||
|         binding.progressBar.visibility = View.GONE | ||||
|         requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item) | ||||
|     } | ||||
|  | ||||
| @@ -492,10 +486,10 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|     } | ||||
|  | ||||
|     fun performClick(): Boolean { | ||||
|         if (binding!!.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || | ||||
|                 binding!!.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { | ||||
|         if (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || | ||||
|                 binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { | ||||
|  | ||||
|             val position : Int = allImages.indexOf(binding!!.webcontent.hitTestResult.extra) | ||||
|             val position : Int = allImages.indexOf(binding.webcontent.hitTestResult.extra) | ||||
|  | ||||
|             val intent = Intent(activity, ImageActivity::class.java) | ||||
|             intent.putExtra("allImages", allImages) | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import android.content.Context | ||||
| import android.graphics.Color | ||||
| import android.graphics.drawable.Drawable | ||||
| import android.graphics.drawable.GradientDrawable | ||||
| import android.os.Build | ||||
| import android.os.Bundle | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| @@ -67,18 +68,22 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { | ||||
|                     val c = Chip(context) | ||||
|                     c.text = tag.tag | ||||
|  | ||||
|                     val gd = GradientDrawable() | ||||
|                     val gdColor = try { | ||||
|                         Color.parseColor(tag.color) | ||||
|                     } catch (e: IllegalArgumentException) { | ||||
|                         e.sendSilentlyWithAcraWithName("color issue " + tag.color) | ||||
|                         resources.getColor(R.color.colorPrimary) | ||||
|                     try { | ||||
|                         val gd = GradientDrawable() | ||||
|                         val gdColor = try { | ||||
|                             Color.parseColor(tag.color) | ||||
|                         } catch (e: IllegalArgumentException) { | ||||
|                             e.sendSilentlyWithAcraWithName("color issue " + tag.color) | ||||
|                             resources.getColor(R.color.colorPrimary) | ||||
|                         } | ||||
|                         gd.setColor(gdColor) | ||||
|                         gd.shape = GradientDrawable.RECTANGLE | ||||
|                         gd.setSize(30, 30) | ||||
|                         gd.cornerRadius = 30F | ||||
|                         c.chipIcon = gd | ||||
|                     } catch (e: Exception) { | ||||
|                         e.sendSilentlyWithAcraWithName("tags > GradientDrawable") | ||||
|                     } | ||||
|                     gd.setColor(gdColor) | ||||
|                     gd.shape = GradientDrawable.RECTANGLE | ||||
|                     gd.setSize(30, 30) | ||||
|                     gd.cornerRadius = 30F | ||||
|                     c.chipIcon = gd | ||||
|  | ||||
|                     c.setOnCloseIconClickListener { | ||||
|                         (it as Chip).isCloseIconVisible = false | ||||
| @@ -127,7 +132,11 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { | ||||
|                                 dataSource: DataSource?, | ||||
|                                 isFirstResource: Boolean | ||||
|                             ): Boolean { | ||||
|                                 c.chipIcon = resource | ||||
|                                 try { | ||||
|                                     c.chipIcon = resource | ||||
|                                 } catch (e: Exception) { | ||||
|                                     e.sendSilentlyWithAcraWithName("sources > onResourceReady") | ||||
|                                 } | ||||
|                                 return false | ||||
|                             } | ||||
|                         }).preload() | ||||
| @@ -157,6 +166,12 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { | ||||
|                         selectedChip = c | ||||
|                     } | ||||
|  | ||||
|                     c.isEnabled = source.error.isBlank() | ||||
|  | ||||
|                     if (source.error.isNotBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||
|                         c.tooltipText = source.error | ||||
|                     } | ||||
|  | ||||
|                     sourceGroup.addView(c) | ||||
|                 } | ||||
|  | ||||
|   | ||||
| @@ -46,7 +46,7 @@ data class ParecelableItem( | ||||
|     val link: String, | ||||
|     val sourcetitle: String, | ||||
|     val tags: String, | ||||
|     val author: String | ||||
|     val author: String? | ||||
| ) : Parcelable { | ||||
|  | ||||
|     companion object { | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
|     tools:context="bou.amine.apps.readerforselfossv2.android.AddSourceActivity"> | ||||
|     tools:context="bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity"> | ||||
|     <LinearLayout | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
| @@ -41,36 +41,20 @@ | ||||
|             app:layout_constraintHorizontal_bias="1.0" | ||||
|             app:layout_constraintVertical_bias="0.0"> | ||||
| 
 | ||||
|             <TextView | ||||
|                 android:text="@string/add_source" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:id="@+id/textView2" | ||||
|                 android:textAppearance="@style/TextAppearance.AppCompat.Large" | ||||
|                 android:textAlignment="center" | ||||
|                 android:layout_marginTop="16dp" | ||||
|                 app:layout_constraintTop_toTopOf="parent" | ||||
|                 android:layout_marginEnd="16dp" | ||||
|                 app:layout_constraintRight_toRightOf="parent" | ||||
|                 android:layout_marginRight="16dp" | ||||
|                 android:layout_marginStart="16dp" | ||||
|                 app:layout_constraintLeft_toLeftOf="parent" | ||||
|                 android:layout_marginLeft="16dp" | ||||
|                 android:gravity="center_horizontal" /> | ||||
| 
 | ||||
|             <EditText | ||||
|                 android:id="@+id/nameInput" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginTop="16dp" | ||||
|                 android:autofillHints="false" | ||||
|                 android:ems="10" | ||||
|                 android:id="@+id/nameInput" | ||||
|                 android:layout_marginTop="32dp" | ||||
|                 app:layout_constraintTop_toBottomOf="@+id/textView2" | ||||
|                 android:hint="@string/add_source_hint_name" | ||||
|                 android:inputType="text" | ||||
|                 android:textColorHint="?android:textColorPrimary" | ||||
|                 app:layout_constraintLeft_toLeftOf="parent" | ||||
|                 app:layout_constraintRight_toRightOf="parent" | ||||
|                 android:inputType="text" | ||||
|                 android:hint="@string/add_source_hint_name" | ||||
|                 android:textColorHint="?android:textColorPrimary" | ||||
|                 android:autofillHints="false" /> | ||||
|                 app:layout_constraintTop_toTopOf="parent" /> | ||||
| 
 | ||||
|             <EditText | ||||
|                 android:layout_width="match_parent" | ||||
| @@ -3,18 +3,40 @@ | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="48dp" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:orientation="vertical"> | ||||
|  | ||||
|  | ||||
|     <Button | ||||
|         android:id="@+id/deleteBtn" | ||||
|         style="@style/Widget.AppCompat.Button.Borderless" | ||||
|         android:layout_width="48dp" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginBottom="8dp" | ||||
|         android:layout_marginEnd="8dp" | ||||
|         android:layout_marginTop="8dp" | ||||
|         android:background="@drawable/ic_remove_circle_outline_black_24dp" | ||||
|         android:backgroundTint="?android:textColorSecondary" | ||||
|         android:contentDescription="@string/remove_source" | ||||
|         android:elevation="4dp" | ||||
|         app:iconSize="34dp" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintTop_toTopOf="parent" | ||||
|         app:layout_constraintVertical_bias="0.0" /> | ||||
|  | ||||
|     <ImageView | ||||
|         android:id="@+id/itemImage" | ||||
|         android:layout_width="36dp" | ||||
|         android:layout_height="36dp" | ||||
|         android:layout_marginBottom="8dp" | ||||
|         android:layout_marginLeft="8dp" | ||||
|         android:layout_marginTop="8dp" | ||||
|         android:importantForAccessibility="no" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" | ||||
|         app:layout_constraintLeft_toLeftOf="parent" | ||||
|         app:layout_constraintTop_toTopOf="parent" /> | ||||
|         app:layout_constraintTop_toTopOf="parent" | ||||
|         app:layout_constraintVertical_bias="0.0" /> | ||||
|  | ||||
|     <TextView | ||||
|         android:id="@+id/sourceTitle" | ||||
| @@ -25,26 +47,30 @@ | ||||
|         android:gravity="start" | ||||
|         android:maxLines="1" | ||||
|         android:textAlignment="textStart" | ||||
|         android:textSize="13sp" | ||||
|         android:textColor="?android:textColorPrimary" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" | ||||
|         android:textSize="13sp" | ||||
|         app:layout_constraintBottom_toTopOf="@+id/errorText" | ||||
|         app:layout_constraintEnd_toStartOf="@+id/deleteBtn" | ||||
|         app:layout_constraintStart_toEndOf="@+id/itemImage" | ||||
|         app:layout_constraintTop_toTopOf="parent" | ||||
|         tools:text="source title" /> | ||||
|  | ||||
|     <Button | ||||
|         android:id="@+id/deleteBtn" | ||||
|         style="@style/Widget.AppCompat.Button.Borderless" | ||||
|         android:layout_width="34dp" | ||||
|         android:layout_height="34dp" | ||||
|         android:layout_marginEnd="8dp" | ||||
|         android:background="@drawable/ic_remove_circle_outline_black_24dp" | ||||
|         android:backgroundTint="?android:textColorSecondary" | ||||
|         android:elevation="4dp" | ||||
|         android:contentDescription="@string/remove_source" | ||||
|     <TextView | ||||
|         android:id="@+id/errorText" | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginBottom="8dp" | ||||
|         android:layout_marginEnd="16dp" | ||||
|         android:layout_marginStart="16dp" | ||||
|         android:layout_marginTop="8dp" | ||||
|         android:textAppearance="@style/TextAppearance.AppCompat.Small" | ||||
|         android:textColor="@color/red" | ||||
|         android:visibility="gone" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintTop_toTopOf="parent" /> | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toBottomOf="@+id/itemImage" | ||||
|         tools:text="Test" | ||||
|         tools:visibility="visible" /> | ||||
|  | ||||
| </androidx.constraintlayout.widget.ConstraintLayout> | ||||
| @@ -20,6 +20,11 @@ | ||||
|           android:orderInCategory="2" | ||||
|           app:showAsAction="ifRoom"/> | ||||
|  | ||||
|     <item android:id="@+id/action_sources" | ||||
|         android:title="@string/menu_home_sources" | ||||
|         android:orderInCategory="97" | ||||
|         app:showAsAction="never"/> | ||||
|  | ||||
|     <item android:id="@+id/action_settings" | ||||
|         android:title="@string/title_activity_settings" | ||||
|         android:orderInCategory="98" | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -6,4 +6,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"禁用自动错误报告 "</string> | ||||
|     <string name="menu_home_filter">筛选器</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -126,4 +126,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -129,4 +129,6 @@ | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -58,6 +58,7 @@ class RepositoryTest { | ||||
|             data = SelfossModel.Stats(NUMBER_ARTICLES, NUMBER_UNREAD, NUMBER_STARRED) | ||||
|         ) | ||||
|  | ||||
|         every { db.itemsQueries.deleteItemsWhereSource(any()) } returns Unit | ||||
|         every { db.itemsQueries.items().executeAsList() } returns generateTestDBItems() | ||||
|         every { db.tagsQueries.deleteAllTags() } returns Unit | ||||
|         every { db.tagsQueries.transaction(any(), any()) } returns Unit | ||||
| @@ -230,7 +231,8 @@ class RepositoryTest { | ||||
|             listOf("tags"), | ||||
|             "spouts\\rss\\fulltextrss", | ||||
|             "", | ||||
|             "b3aa8a664d08eb15d6ff1db2fa83e0d9.png" | ||||
|             "b3aa8a664d08eb15d6ff1db2fa83e0d9.png", | ||||
|             SelfossModel.SourceParams("url") | ||||
|         )) | ||||
|         runBlocking { | ||||
|             repository.getNewerItems() | ||||
| @@ -550,7 +552,8 @@ class RepositoryTest { | ||||
|                 listOf("Test", "second"), | ||||
|                 "spouts\\rss\\fulltextrss", | ||||
|                 "", | ||||
|                 "d8c92cdb1ef119ea85c4b9205c879ca7.png" | ||||
|                 "d8c92cdb1ef119ea85c4b9205c879ca7.png", | ||||
|                 SelfossModel.SourceParams("url") | ||||
|             ), | ||||
|             SelfossModel.Source( | ||||
|                 2, | ||||
| @@ -558,7 +561,8 @@ class RepositoryTest { | ||||
|                 listOf("second"), | ||||
|                 "spouts\\rss\\fulltextrss", | ||||
|                 "", | ||||
|                 "b3aa8a664d08eb15d6ff1db2fa83e0d9.png" | ||||
|                 "b3aa8a664d08eb15d6ff1db2fa83e0d9.png", | ||||
|                 SelfossModel.SourceParams("url") | ||||
|             ) | ||||
|         ) | ||||
|         val sourcesDB = listOf( | ||||
| @@ -568,7 +572,8 @@ class RepositoryTest { | ||||
|                 "Test,second", | ||||
|                 "spouts\\rss\\fulltextrss", | ||||
|                 "", | ||||
|                 "d8c92cdb1ef119ea85c4b9205c879ca7.png" | ||||
|                 "d8c92cdb1ef119ea85c4b9205c879ca7.png", | ||||
|                 "url" | ||||
|             ), | ||||
|             SOURCE( | ||||
|                 "2", | ||||
| @@ -576,7 +581,8 @@ class RepositoryTest { | ||||
|                 "second", | ||||
|                 "spouts\\rss\\fulltextrss", | ||||
|                 "", | ||||
|                 "b3aa8a664d08eb15d6ff1db2fa83e0d9.png" | ||||
|                 "b3aa8a664d08eb15d6ff1db2fa83e0d9.png", | ||||
|                 "url" | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
| @@ -706,7 +712,7 @@ class RepositoryTest { | ||||
|  | ||||
|     @Test | ||||
|     fun create_source() { | ||||
|         coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns | ||||
|         coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns | ||||
|                 SuccessResponse(true) | ||||
|  | ||||
|         initializeRepository() | ||||
| @@ -717,7 +723,6 @@ class RepositoryTest { | ||||
|                 "https://test.com/feed", | ||||
|                 "spouts\\rss\\fulltextrss", | ||||
|                 "Test, New", | ||||
|                 "" | ||||
|             ) | ||||
|         } | ||||
|  | ||||
| @@ -727,7 +732,6 @@ class RepositoryTest { | ||||
|                 any(), | ||||
|                 any(), | ||||
|                 any(), | ||||
|                 any(), | ||||
|             ) | ||||
|         } | ||||
|         assertSame(true, response) | ||||
| @@ -735,7 +739,7 @@ class RepositoryTest { | ||||
|  | ||||
|     @Test | ||||
|     fun create_source_but_response_fails() { | ||||
|         coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns | ||||
|         coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns | ||||
|                 SuccessResponse(false) | ||||
|  | ||||
|         initializeRepository() | ||||
| @@ -745,8 +749,7 @@ class RepositoryTest { | ||||
|                 "test", | ||||
|                 "https://test.com/feed", | ||||
|                 "spouts\\rss\\fulltextrss", | ||||
|                 "Test, New", | ||||
|                 "" | ||||
|                 "Test, New" | ||||
|             ) | ||||
|         } | ||||
|  | ||||
| @@ -755,8 +758,7 @@ class RepositoryTest { | ||||
|                 any(), | ||||
|                 any(), | ||||
|                 any(), | ||||
|                 any(), | ||||
|                 any(), | ||||
|                 any() | ||||
|             ) | ||||
|         } | ||||
|         assertSame(false, response) | ||||
| @@ -764,7 +766,7 @@ class RepositoryTest { | ||||
|  | ||||
|     @Test | ||||
|     fun create_source_without_connection() { | ||||
|         coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns | ||||
|         coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns | ||||
|                 SuccessResponse(true) | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
| @@ -774,8 +776,7 @@ class RepositoryTest { | ||||
|                 "test", | ||||
|                 "https://test.com/feed", | ||||
|                 "spouts\\rss\\fulltextrss", | ||||
|                 "Test, New", | ||||
|                 "" | ||||
|                 "Test, New" | ||||
|             ) | ||||
|         } | ||||
|  | ||||
| @@ -785,7 +786,6 @@ class RepositoryTest { | ||||
|                 any(), | ||||
|                 any(), | ||||
|                 any(), | ||||
|                 any() | ||||
|             ) | ||||
|         } | ||||
|         assertSame(false, response) | ||||
| @@ -798,10 +798,11 @@ class RepositoryTest { | ||||
|         initializeRepository() | ||||
|         var response: Boolean | ||||
|         runBlocking { | ||||
|             response = repository.deleteSource(5) | ||||
|             response = repository.deleteSource(5, "src") | ||||
|         } | ||||
|  | ||||
|         coVerify(exactly = 1) { api.deleteSource(5) } | ||||
|         coVerify(exactly = 1) { db.itemsQueries.deleteItemsWhereSource("src") } | ||||
|         assertSame(true, response) | ||||
|     } | ||||
|  | ||||
| @@ -812,10 +813,11 @@ class RepositoryTest { | ||||
|         initializeRepository() | ||||
|         var response: Boolean | ||||
|         runBlocking { | ||||
|             response = repository.deleteSource(5) | ||||
|             response = repository.deleteSource(5, "src") | ||||
|         } | ||||
|  | ||||
|         coVerify(exactly = 1) { api.deleteSource(5) } | ||||
|         coVerify(exactly = 0) { db.itemsQueries.deleteItemsWhereSource("src") } | ||||
|         assertSame(false, response) | ||||
|     } | ||||
|  | ||||
| @@ -826,10 +828,11 @@ class RepositoryTest { | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         var response: Boolean | ||||
|         runBlocking { | ||||
|             response = repository.deleteSource(5) | ||||
|             response = repository.deleteSource(5, "src") | ||||
|         } | ||||
|  | ||||
|         coVerify(exactly = 0) { api.deleteSource(5) } | ||||
|         coVerify(exactly = 1) { db.itemsQueries.deleteItemsWhereSource("src") } | ||||
|         assertSame(false, response) | ||||
|     } | ||||
|  | ||||
| @@ -1030,7 +1033,8 @@ class RepositoryTest { | ||||
|                 listOf("Test", "second"), | ||||
|                 "spouts\\rss\\fulltextrss", | ||||
|                 "", | ||||
|                 "d8c92cdb1ef119ea85c4b9205c879ca7.png" | ||||
|                 "d8c92cdb1ef119ea85c4b9205c879ca7.png", | ||||
|                 SelfossModel.SourceParams("url") | ||||
|             ) | ||||
|         ) | ||||
|         repository.searchFilter = "search" | ||||
|   | ||||
| @@ -17,12 +17,11 @@ plugins { | ||||
|  | ||||
| allprojects { | ||||
|     repositories { | ||||
|         maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")} | ||||
|         // IMPORTANT : Add back when new library added | ||||
|         // google() | ||||
|         // mavenCentral() | ||||
|         // jcenter() | ||||
|         // maven { url = uri("https://www.jitpack.io") } | ||||
|         // maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")} | ||||
|         google() | ||||
|         mavenCentral() | ||||
|         jcenter() | ||||
|         maven { url = uri("https://www.jitpack.io") } | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -2,20 +2,18 @@ val pushCache: String by settings | ||||
|  | ||||
| pluginManagement { | ||||
|     repositories { | ||||
|         maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")} | ||||
|         // IMPORTANT : Add back when new plugin added | ||||
|         // google() | ||||
|         // gradlePluginPortal() | ||||
|         // mavenCentral() | ||||
|         // maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")} | ||||
|         google() | ||||
|         gradlePluginPortal() | ||||
|         mavenCentral() | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencyResolutionManagement { | ||||
|     repositories { | ||||
|         maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")} | ||||
|         // IMPORTANT : Add back when new library added | ||||
|         // google() | ||||
|         // mavenCentral() | ||||
|         // maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")} | ||||
|         google() | ||||
|         mavenCentral() | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -6,7 +6,7 @@ class MercuryModel { | ||||
|  | ||||
|     @Serializable | ||||
|     class ParsedContent( | ||||
|         val title: String, | ||||
|         val title: String?, | ||||
|         val content: String?, | ||||
|         val lead_image_url: String?, | ||||
|         val url: String | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor | ||||
| import kotlinx.serialization.descriptors.SerialDescriptor | ||||
| import kotlinx.serialization.encoding.Decoder | ||||
| import kotlinx.serialization.encoding.Encoder | ||||
| import kotlinx.serialization.encoding.encodeCollection | ||||
| import kotlinx.serialization.json.* | ||||
|  | ||||
| class SelfossModel { | ||||
| @@ -55,7 +56,12 @@ class SelfossModel { | ||||
|         val tags: List<String>, | ||||
|         val spout: String, | ||||
|         val error: String, | ||||
|         val icon: String? | ||||
|         val icon: String?, | ||||
|         val params: SourceParams? | ||||
|     ) | ||||
|     @Serializable | ||||
|     data class SourceParams( | ||||
|         val url: String | ||||
|     ) | ||||
|     @Serializable | ||||
|     data class Item( | ||||
| @@ -73,7 +79,7 @@ class SelfossModel { | ||||
|         val sourcetitle: String, | ||||
|         @Serializable(with = TagsListSerializer::class) | ||||
|         val tags: List<String>, | ||||
|         val author: String | ||||
|         val author: String? | ||||
|     ) { | ||||
|         // TODO: maybe find a better way to handle these kind of urls | ||||
|         fun getLinkDecoded(): String { | ||||
| @@ -104,7 +110,7 @@ class SelfossModel { | ||||
|  | ||||
|         fun sourceAuthorAndDate(): String { | ||||
|             var txt = this.sourcetitle.getHtmlDecoded() | ||||
|             if (this.author.isNotEmpty()) { | ||||
|             if (!this.author.isNullOrBlank()) { | ||||
|                 txt += " (by ${this.author}) " | ||||
|             } | ||||
|             txt += DateUtils.parseRelativeDate(this.datetime) | ||||
| @@ -122,7 +128,7 @@ class SelfossModel { | ||||
|     object TagsListSerializer : KSerializer<List<String>> { | ||||
|         override fun deserialize(decoder: Decoder): List<String> { | ||||
|             return when(val json = ((decoder as JsonDecoder).decodeJsonElement())) { | ||||
|                 is JsonArray -> json.toList().map { it.toString() } | ||||
|                 is JsonArray -> json.toList().map { it.toString().replace("^\"|\"$".toRegex(), "") } | ||||
|                 else -> json.toString().split(",") | ||||
|             } | ||||
|  | ||||
| @@ -132,7 +138,7 @@ class SelfossModel { | ||||
|             get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING) | ||||
|  | ||||
|         override fun serialize(encoder: Encoder, value: List<String>) { | ||||
|             TODO("Not yet implemented") | ||||
|             encoder.encodeCollection(PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING), value.size) { this.toString() } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -48,6 +48,7 @@ class Repository( | ||||
|     private var fetchedTags = false | ||||
|  | ||||
|     private var _readerItems = ArrayList<SelfossModel.Item>() | ||||
|     private var _selectedSource: SelfossModel.Source? = null | ||||
|  | ||||
|     suspend fun getNewerItems(): ArrayList<SelfossModel.Item> { | ||||
|         // TODO: Use the updatedSince parameter | ||||
| @@ -343,7 +344,6 @@ class Repository( | ||||
|         url: String, | ||||
|         spout: String, | ||||
|         tags: String, | ||||
|         filter: String | ||||
|     ): Boolean { | ||||
|         var response = false | ||||
|         if (isNetworkAvailable()) { | ||||
| @@ -352,20 +352,41 @@ class Repository( | ||||
|                 url, | ||||
|                 spout, | ||||
|                 tags, | ||||
|                 filter | ||||
|             ).isSuccess == true | ||||
|         } | ||||
|  | ||||
|         return response | ||||
|     } | ||||
|  | ||||
|     suspend fun deleteSource(id: Int): Boolean { | ||||
|     suspend fun updateSource( | ||||
|         id: Int, | ||||
|         title: String, | ||||
|         url: String, | ||||
|         spout: String, | ||||
|         tags: String | ||||
|     ): Boolean { | ||||
|         var response = false | ||||
|         if (isNetworkAvailable()) { | ||||
|             response = api.updateSourceForVersion(id, title, url, spout, tags).isSuccess == true | ||||
|         } | ||||
|  | ||||
|         return response | ||||
|     } | ||||
|  | ||||
|     suspend fun deleteSource(id: Int, title: String): Boolean { | ||||
|         var success = false | ||||
|         if (isNetworkAvailable()) { | ||||
|             val response = api.deleteSource(id) | ||||
|             success = response.isSuccess | ||||
|         } | ||||
|  | ||||
|         // We filter on success or if the network isn't available | ||||
|         if (success || !isNetworkAvailable()) { | ||||
|             items = ArrayList(items.filter { it.sourcetitle != title }) | ||||
|             setReaderItems(items) | ||||
|             db.itemsQueries.deleteItemsWhereSource(title) | ||||
|         } | ||||
|  | ||||
|         return success | ||||
|     } | ||||
|  | ||||
| @@ -504,6 +525,7 @@ class Repository( | ||||
|             item.link, | ||||
|             item.sourcetitle, | ||||
|             item.tags.joinToString(","), | ||||
|             item.author, | ||||
|             item.id.toString() | ||||
|         ) | ||||
|  | ||||
| @@ -568,4 +590,20 @@ class Repository( | ||||
|     fun getReaderItems(): ArrayList<SelfossModel.Item> { | ||||
|         return _readerItems | ||||
|     } | ||||
|  | ||||
|     fun migrate(driverFactory: DriverFactory) { | ||||
|         ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1) | ||||
|     } | ||||
|  | ||||
|     fun setSelectedSource(source: SelfossModel.Source) { | ||||
|         _selectedSource = source | ||||
|     } | ||||
|  | ||||
|     fun unsetSelectedSource() { | ||||
|         _selectedSource = null | ||||
|     } | ||||
|  | ||||
|     fun getSelectedSource(): SelfossModel.Source? { | ||||
|         return _selectedSource | ||||
|     } | ||||
| } | ||||
| @@ -23,6 +23,9 @@ suspend fun maybeResponse(r: HttpResponse?): SuccessResponse { | ||||
|     return if (r != null && r.status.isSuccess()) { | ||||
|         r.body() | ||||
|     } else { | ||||
|         if (r != null) { | ||||
|             Napier.i("Error ${r.status}", tag = "maybeResponse") | ||||
|         } | ||||
|         SuccessResponse(false) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -76,10 +76,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|  | ||||
|     // Api version was introduces after the POST login, so when there is a version, it should be available | ||||
|     private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1 | ||||
|     private fun hasLoginInfo() = appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword().isNotEmpty() | ||||
|     private fun hasLoginInfo() = | ||||
|         appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword() | ||||
|             .isNotEmpty() | ||||
|  | ||||
|     suspend fun login(): SuccessResponse = | ||||
|         if (appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword().isNotEmpty()) { | ||||
|         if (appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword() | ||||
|                 .isNotEmpty() | ||||
|         ) { | ||||
|             if (shouldHavePostLogin()) { | ||||
|                 postLogin() | ||||
|             } else { | ||||
| @@ -99,7 +103,9 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|         parameter("password", appSettingsService.getPassword()) | ||||
|     }) | ||||
|  | ||||
|     private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0 | ||||
|     private fun shouldHaveNewLogout() = | ||||
|         appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0 | ||||
|  | ||||
|     suspend fun logout(): SuccessResponse = | ||||
|         if (shouldHaveNewLogout()) { | ||||
|             doLogout() | ||||
| @@ -107,7 +113,8 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|             maybeLogoutIfAvailable() | ||||
|         } | ||||
|  | ||||
|     private suspend fun maybeLogoutIfAvailable() = responseOrSuccessIf404(client.tryToGet(url("/logout"))) | ||||
|     private suspend fun maybeLogoutIfAvailable() = | ||||
|         responseOrSuccessIf404(client.tryToGet(url("/logout"))) | ||||
|  | ||||
|     private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current"))) | ||||
|  | ||||
| @@ -236,13 +243,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|         url: String, | ||||
|         spout: String, | ||||
|         tags: String, | ||||
|         filter: String | ||||
|     ): SuccessResponse = | ||||
|         maybeResponse( | ||||
|             if (appSettingsService.getApiVersion() > 1) { | ||||
|                 createSource("tags[]", title, url, spout, tags, filter) | ||||
|                 createSource("tags[]", title, url, spout, tags) | ||||
|             } else { | ||||
|                 createSource("tags", title, url, spout, tags, filter) | ||||
|                 createSource("tags", title, url, spout, tags) | ||||
|             } | ||||
|         ) | ||||
|  | ||||
| @@ -251,8 +257,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|         title: String, | ||||
|         url: String, | ||||
|         spout: String, | ||||
|         tags: String, | ||||
|         filter: String | ||||
|         tags: String | ||||
|     ): HttpResponse? = | ||||
|         client.tryToSubmitForm( | ||||
|             url = url("/source"), | ||||
| @@ -265,7 +270,43 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                 append("url", url) | ||||
|                 append("spout", spout) | ||||
|                 append(tagsParamName, tags) | ||||
|                 append("filter", filter) | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|     suspend fun updateSourceForVersion( | ||||
|         id: Int, | ||||
|         title: String, | ||||
|         url: String, | ||||
|         spout: String, | ||||
|         tags: String | ||||
|     ): SuccessResponse = | ||||
|         maybeResponse( | ||||
|             if (appSettingsService.getApiVersion() > 1) { | ||||
|                 updateSource(id, "tags[]", title, url, spout, tags) | ||||
|             } else { | ||||
|                 updateSource(id, "tags", title, url, spout, tags) | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|     private suspend fun updateSource( | ||||
|         id: Int, | ||||
|         tagsParamName: String, | ||||
|         title: String, | ||||
|         url: String, | ||||
|         spout: String, | ||||
|         tags: String, | ||||
|     ): HttpResponse? = | ||||
|         client.tryToSubmitForm( | ||||
|             url = url("/source/$id"), | ||||
|             formParameters = Parameters.build { | ||||
|                 if (!shouldHavePostLogin()) { | ||||
|                     append("username", appSettingsService.getUserName()) | ||||
|                     append("password", appSettingsService.getPassword()) | ||||
|                 } | ||||
|                 append("title", title) | ||||
|                 append("url", url) | ||||
|                 append("spout", spout) | ||||
|                 append(tagsParamName, tags) | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|   | ||||
| @@ -19,7 +19,8 @@ fun SOURCE.toView(): SelfossModel.Source = | ||||
|         this.tags.split(","), | ||||
|         this.spout, | ||||
|         this.error, | ||||
|         this.icon | ||||
|         this.icon, | ||||
|         if (this.url != null) SelfossModel.SourceParams(this.url) else null | ||||
|     ) | ||||
|  | ||||
| fun SelfossModel.Source.toEntity(): SOURCE = | ||||
| @@ -29,7 +30,8 @@ fun SelfossModel.Source.toEntity(): SOURCE = | ||||
|         this.tags.joinToString(","), | ||||
|         this.spout, | ||||
|         this.error, | ||||
|         this.icon.orEmpty() | ||||
|         this.icon.orEmpty(), | ||||
|         this.params?.url | ||||
|     ) | ||||
|  | ||||
| fun SelfossModel.Tag.toEntity(): TAG = | ||||
|   | ||||
| @@ -1 +1,6 @@ | ||||
| ALTER TABLE ITEM ADD COLUMN `author` TEXT NOT NULL; | ||||
| CREATE TABLE ITEM_BACKUP AS SELECT `id`, `datetime`, `title`, `content`, | ||||
|  `unread`, `starred`, `thumbnail`, `icon`, `link`, `sourcetitle`, | ||||
|  `tags` FROM ITEM; | ||||
| ALTER TABLE ITEM_BACKUP ADD COLUMN `author` TEXT; | ||||
| DROP TABLE ITEM; | ||||
| ALTER TABLE ITEM_BACKUP RENAME TO ITEM; | ||||
| @@ -0,0 +1 @@ | ||||
| ALTER TABLE SOURCE ADD COLUMN `url` TEXT; | ||||
| @@ -10,7 +10,7 @@ CREATE TABLE ITEM ( | ||||
|     `link` TEXT NOT NULL, | ||||
|     `sourcetitle` TEXT NOT NULL, | ||||
|     `tags` TEXT NOT NULL, | ||||
|     `author` TEXT NOT NULL, | ||||
|     `author` TEXT, | ||||
|     PRIMARY KEY(`id`) | ||||
| ); | ||||
|  | ||||
| @@ -27,5 +27,8 @@ INSERT OR REPLACE INTO ITEM VALUES ?; | ||||
| deleteItem: | ||||
| DELETE FROM ITEM WHERE `id` = ?; | ||||
|  | ||||
| deleteItemsWhereSource: | ||||
| DELETE FROM ITEM WHERE `sourcetitle` = ?; | ||||
|  | ||||
| updateItem: | ||||
| UPDATE ITEM SET `datetime` = ?, `title` = ?, `content` = ?, `unread` = ?, `starred` = ?, `thumbnail` = ?, `icon` = ?, `link` = ?, `sourcetitle` = ?, `tags` = ? WHERE `id` = ?; | ||||
| UPDATE ITEM SET `datetime` = ?, `title` = ?, `content` = ?, `unread` = ?, `starred` = ?, `thumbnail` = ?, `icon` = ?, `link` = ?, `sourcetitle` = ?, `tags` = ?, `author` = ? WHERE `id` = ?; | ||||
| @@ -5,6 +5,7 @@ CREATE TABLE SOURCE ( | ||||
|     `spout` TEXT NOT NULL, | ||||
|     `error` TEXT NOT NULL, | ||||
|     `icon` TEXT NOT NULL, | ||||
|     `url` TEXT, | ||||
|     PRIMARY KEY(`id`) | ||||
| ); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user