Compare commits
	
		
			12 Commits
		
	
	
		
			ab4b1ae644
			...
			v122113161
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | a0b5e2052b | ||
|  | 87d1ef2bce | ||
|  | 537a6d3a0b | ||
| dbe97f564e | |||
|  | 3a3bf03114 | ||
| c09a32e9ad | |||
| b02a588dff | |||
|  | a4527940b8 | ||
|  | 9e8a25ed3e | ||
|  | 8ea46e146b | ||
|  | 5ecf3c3f87 | ||
|  | 325f103417 | 
| @@ -8,7 +8,7 @@ steps: | |||||||
|     commands: |     commands: | ||||||
|       - echo "---------------------------------------------------------" |       - echo "---------------------------------------------------------" | ||||||
|       - echo "Configure gradle..." |       - echo "Configure gradle..." | ||||||
|       - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nappLoginUrl=\"URL\"\nappLoginUsername=\"LOGIN\"\nappLoginPassword=\"PASS\"\npushCache=false" >> ~/.gradle/gradle.properties |       - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nappLoginUrl=\"URL\"\nappLoginUsername=\"LOGIN\"\nappLoginPassword=\"PASS\"\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties | ||||||
|       - echo "---------------------------------------------------------" |       - echo "---------------------------------------------------------" | ||||||
|       - echo "Analysing..." |       - echo "Analysing..." | ||||||
|       - ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN |       - ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN | ||||||
| @@ -90,7 +90,7 @@ steps: | |||||||
|     commands: |     commands: | ||||||
|       - echo "---------------------------------------------------------" |       - echo "---------------------------------------------------------" | ||||||
|       - echo "Configure gradle..." |       - echo "Configure gradle..." | ||||||
|       - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nappLoginUrl=\"URL\"\nappLoginUsername=\"LOGIN\"\nappLoginPassword=\"PASS\"\npushCache=false" >> ~/.gradle/gradle.properties |       - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nappLoginUrl=\"URL\"\nappLoginUsername=\"LOGIN\"\nappLoginPassword=\"PASS\"\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties | ||||||
|       - echo "---------------------------------------------------------" |       - echo "---------------------------------------------------------" | ||||||
|       - echo "Generate APK" |       - echo "Generate APK" | ||||||
|       - ./gradlew :androidApp:assembleGithubConfigRelease  -P pushCache=false |       - ./gradlew :androidApp:assembleGithubConfigRelease  -P pushCache=false | ||||||
|   | |||||||
| @@ -142,12 +142,6 @@ dependencies { | |||||||
|     implementation("com.mikepenz:aboutlibraries-core:10.5.1") |     implementation("com.mikepenz:aboutlibraries-core:10.5.1") | ||||||
|     implementation("com.mikepenz:aboutlibraries:10.5.1") |     implementation("com.mikepenz:aboutlibraries:10.5.1") | ||||||
|  |  | ||||||
|     // Retrofit + http logging + okhttp |  | ||||||
|     implementation("com.squareup.retrofit2:retrofit:2.9.0") |  | ||||||
|     implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3") |  | ||||||
|     implementation("com.squareup.retrofit2:converter-gson:2.9.0") |  | ||||||
|     implementation("com.burgstaller:okhttp-digest:2.5") |  | ||||||
|  |  | ||||||
|     // Material-ish things |     // Material-ish things | ||||||
|     implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0") |     implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0") | ||||||
|     implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1") |     implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1") | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								androidApp/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								androidApp/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							| @@ -30,15 +30,8 @@ | |||||||
|     <fields>; |     <fields>; | ||||||
| } | } | ||||||
|  |  | ||||||
| -dontwarn okio.** |  | ||||||
| -dontwarn retrofit2.Platform$Java8 |  | ||||||
| -keep class retrofit.** { *; } |  | ||||||
| -keepclasseswithmembers class * { |  | ||||||
|     @retrofit.http.* <methods>; |  | ||||||
| } |  | ||||||
| -keepattributes *Annotation*,Signature | -keepattributes *Annotation*,Signature | ||||||
| -keepattributes Exceptions | -keepattributes Exceptions | ||||||
| -dontwarn okio.** |  | ||||||
| -dontwarn javax.annotation.Nullable | -dontwarn javax.annotation.Nullable | ||||||
| -dontwarn javax.annotation.ParametersAreNonnullByDefault | -dontwarn javax.annotation.ParametersAreNonnullByDefault | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package bou.amine.apps.readerforselfossv2.android | |||||||
|  |  | ||||||
| import android.animation.Animator | import android.animation.Animator | ||||||
| import android.animation.AnimatorListenerAdapter | import android.animation.AnimatorListenerAdapter | ||||||
|  | import android.annotation.SuppressLint | ||||||
| import android.content.Intent | import android.content.Intent | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
| import android.text.TextUtils | import android.text.TextUtils | ||||||
| @@ -38,7 +39,8 @@ class LoginActivity : AppCompatActivity(), DIAware { | |||||||
|  |  | ||||||
|     override fun onCreate(savedInstanceState: Bundle?) { |     override fun onCreate(savedInstanceState: Bundle?) { | ||||||
|         super.onCreate(savedInstanceState) |         super.onCreate(savedInstanceState) | ||||||
|         AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) |  | ||||||
|  |         handleTheme() | ||||||
|  |  | ||||||
|         binding = ActivityLoginBinding.inflate(layoutInflater) |         binding = ActivityLoginBinding.inflate(layoutInflater) | ||||||
|         val view = binding.root |         val view = binding.root | ||||||
| @@ -56,6 +58,11 @@ class LoginActivity : AppCompatActivity(), DIAware { | |||||||
|         handleActions() |         handleActions() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @SuppressLint("WrongConstant") // Constant is fetched from the settings | ||||||
|  |     private fun handleTheme() { | ||||||
|  |         AppCompatDelegate.setDefaultNightMode(appSettingsService.getCurrentTheme()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private fun handleActions() { |     private fun handleActions() { | ||||||
|  |  | ||||||
|         binding.passwordView.setOnEditorActionListener( |         binding.passwordView.setOnEditorActionListener( | ||||||
|   | |||||||
| @@ -51,7 +51,6 @@ class MyApp : MultiDexApplication(), DIAware { | |||||||
|     private val viewModel: AppViewModel by instance() |     private val viewModel: AppViewModel by instance() | ||||||
|     private val connectivityStatus: ConnectivityStatus by instance() |     private val connectivityStatus: ConnectivityStatus by instance() | ||||||
|     private val driverFactory: DriverFactory by instance() |     private val driverFactory: DriverFactory by instance() | ||||||
|     private val appSettingsService : AppSettingsService by instance() |  | ||||||
|  |  | ||||||
|     // TODO: handle with the "previous" way |     // TODO: handle with the "previous" way | ||||||
|     private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true) |     private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true) | ||||||
| @@ -126,29 +125,15 @@ class MyApp : MultiDexApplication(), DIAware { | |||||||
|         val oldHandler = Thread.getDefaultUncaughtExceptionHandler() |         val oldHandler = Thread.getDefaultUncaughtExceptionHandler() | ||||||
|  |  | ||||||
|         Thread.setDefaultUncaughtExceptionHandler { thread, e -> |         Thread.setDefaultUncaughtExceptionHandler { thread, e -> | ||||||
|             if (e is java.lang.NoClassDefFoundError && e.stackTrace.asList().any { |             if (e is NoClassDefFoundError && e.stackTrace.asList().any { | ||||||
|                     it.toString().contains("android.view.ViewDebug") |                     it.toString().contains("android.view.ViewDebug") | ||||||
|                 }) { |                 }) { | ||||||
|                 Unit |  | ||||||
|             } else { |             } else { | ||||||
|                 oldHandler.uncaughtException(thread, e) |                 oldHandler.uncaughtException(thread, e) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun onConfigurationChanged(newConfig: Configuration) { |  | ||||||
|         super.onConfigurationChanged(newConfig) |  | ||||||
|         if (appSettingsService.getCurrentTheme() == MODE_NIGHT_FOLLOW_SYSTEM) { |  | ||||||
|             var mode = when (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK) { |  | ||||||
|                 Configuration.UI_MODE_NIGHT_YES -> MODE_NIGHT_YES |  | ||||||
|                 else -> MODE_NIGHT_NO |  | ||||||
|             } |  | ||||||
|             setDefaultNightMode(mode) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     class AppLifeCycleObserver(val connectivityStatus: ConnectivityStatus, val repository: Repository) : DefaultLifecycleObserver { |     class AppLifeCycleObserver(val connectivityStatus: ConnectivityStatus, val repository: Repository) : DefaultLifecycleObserver { | ||||||
|  |  | ||||||
|         override fun onResume(owner: LifecycleOwner) { |         override fun onResume(owner: LifecycleOwner) { | ||||||
|   | |||||||
| @@ -1,35 +0,0 @@ | |||||||
| package bou.amine.apps.readerforselfossv2.android.api.mercury |  | ||||||
|  |  | ||||||
| import com.google.gson.GsonBuilder |  | ||||||
| import okhttp3.OkHttpClient |  | ||||||
| import okhttp3.logging.HttpLoggingInterceptor |  | ||||||
| import retrofit2.Call |  | ||||||
| import retrofit2.Retrofit |  | ||||||
| import retrofit2.converter.gson.GsonConverterFactory |  | ||||||
|  |  | ||||||
| class MercuryApi() { |  | ||||||
|     private val service: MercuryService |  | ||||||
|  |  | ||||||
|     init { |  | ||||||
|  |  | ||||||
|         val interceptor = HttpLoggingInterceptor() |  | ||||||
|         interceptor.level = HttpLoggingInterceptor.Level.NONE |  | ||||||
|         val client = OkHttpClient.Builder().addInterceptor(interceptor).build() |  | ||||||
|  |  | ||||||
|         val gson = GsonBuilder() |  | ||||||
|             .setLenient() |  | ||||||
|             .create() |  | ||||||
|         val retrofit = |  | ||||||
|             Retrofit |  | ||||||
|                 .Builder() |  | ||||||
|                 .baseUrl("https://www.amine-louveau.fr") |  | ||||||
|                 .client(client) |  | ||||||
|                 .addConverterFactory(GsonConverterFactory.create(gson)) |  | ||||||
|                 .build() |  | ||||||
|         service = retrofit.create(MercuryService::class.java) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun parseUrl(url: String): Call<ParsedContent> { |  | ||||||
|         return service.parseUrl(url) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,59 +0,0 @@ | |||||||
| package bou.amine.apps.readerforselfossv2.android.api.mercury |  | ||||||
|  |  | ||||||
| import android.os.Parcel |  | ||||||
| import android.os.Parcelable |  | ||||||
| import com.google.gson.annotations.SerializedName |  | ||||||
|  |  | ||||||
| class ParsedContent( |  | ||||||
|     @SerializedName("title") val title: String, |  | ||||||
|     @SerializedName("content") val content: String?, |  | ||||||
|     @SerializedName("date_published") val date_published: String, |  | ||||||
|     @SerializedName("lead_image_url") val lead_image_url: String?, |  | ||||||
|     @SerializedName("dek") val dek: String, |  | ||||||
|     @SerializedName("url") val url: String, |  | ||||||
|     @SerializedName("domain") val domain: String, |  | ||||||
|     @SerializedName("excerpt") val excerpt: String, |  | ||||||
|     @SerializedName("total_pages") val total_pages: Int, |  | ||||||
|     @SerializedName("rendered_pages") val rendered_pages: Int, |  | ||||||
|     @SerializedName("next_page_url") val next_page_url: String |  | ||||||
| ) : Parcelable { |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         @JvmField |  | ||||||
|         val CREATOR: Parcelable.Creator<ParsedContent> = |  | ||||||
|             object : Parcelable.Creator<ParsedContent> { |  | ||||||
|                 override fun createFromParcel(source: Parcel): ParsedContent = ParsedContent(source) |  | ||||||
|                 override fun newArray(size: Int): Array<ParsedContent?> = arrayOfNulls(size) |  | ||||||
|             } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     constructor(source: Parcel) : this( |  | ||||||
|         title = source.readString().orEmpty(), |  | ||||||
|         content = source.readString(), |  | ||||||
|         date_published = source.readString().orEmpty(), |  | ||||||
|         lead_image_url = source.readString(), |  | ||||||
|         dek = source.readString().orEmpty(), |  | ||||||
|         url = source.readString().orEmpty(), |  | ||||||
|         domain = source.readString().orEmpty(), |  | ||||||
|         excerpt = source.readString().orEmpty(), |  | ||||||
|         total_pages = source.readInt(), |  | ||||||
|         rendered_pages = source.readInt(), |  | ||||||
|         next_page_url = source.readString().orEmpty() |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     override fun describeContents() = 0 |  | ||||||
|  |  | ||||||
|     override fun writeToParcel(dest: Parcel, flags: Int) { |  | ||||||
|         dest.writeString(title) |  | ||||||
|         dest.writeString(content) |  | ||||||
|         dest.writeString(date_published) |  | ||||||
|         dest.writeString(lead_image_url) |  | ||||||
|         dest.writeString(dek) |  | ||||||
|         dest.writeString(url) |  | ||||||
|         dest.writeString(domain) |  | ||||||
|         dest.writeString(excerpt) |  | ||||||
|         dest.writeInt(total_pages) |  | ||||||
|         dest.writeInt(rendered_pages) |  | ||||||
|         dest.writeString(next_page_url) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| package bou.amine.apps.readerforselfossv2.android.api.mercury |  | ||||||
|  |  | ||||||
| import retrofit2.Call |  | ||||||
| import retrofit2.http.GET |  | ||||||
| import retrofit2.http.Query |  | ||||||
|  |  | ||||||
| interface MercuryService { |  | ||||||
|     @GET("parser.php") |  | ||||||
|     fun parseUrl(@Query("link") link: String): Call<ParsedContent> |  | ||||||
| } |  | ||||||
| @@ -21,8 +21,6 @@ import androidx.core.widget.NestedScrollView | |||||||
| import androidx.fragment.app.Fragment | import androidx.fragment.app.Fragment | ||||||
| import bou.amine.apps.readerforselfossv2.android.ImageActivity | import bou.amine.apps.readerforselfossv2.android.ImageActivity | ||||||
| import bou.amine.apps.readerforselfossv2.android.R | import bou.amine.apps.readerforselfossv2.android.R | ||||||
| import bou.amine.apps.readerforselfossv2.android.api.mercury.MercuryApi |  | ||||||
| import bou.amine.apps.readerforselfossv2.android.api.mercury.ParsedContent |  | ||||||
| import bou.amine.apps.readerforselfossv2.android.databinding.FragmentArticleBinding | import bou.amine.apps.readerforselfossv2.android.databinding.FragmentArticleBinding | ||||||
| import bou.amine.apps.readerforselfossv2.android.model.ParecelableItem | import bou.amine.apps.readerforselfossv2.android.model.ParecelableItem | ||||||
| import bou.amine.apps.readerforselfossv2.android.model.toModel | import bou.amine.apps.readerforselfossv2.android.model.toModel | ||||||
| @@ -32,6 +30,7 @@ import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask | |||||||
| import bou.amine.apps.readerforselfossv2.android.utils.shareLink | import bou.amine.apps.readerforselfossv2.android.utils.shareLink | ||||||
| import bou.amine.apps.readerforselfossv2.model.SelfossModel | import bou.amine.apps.readerforselfossv2.model.SelfossModel | ||||||
| import bou.amine.apps.readerforselfossv2.repository.Repository | import bou.amine.apps.readerforselfossv2.repository.Repository | ||||||
|  | import bou.amine.apps.readerforselfossv2.rest.MercuryApi | ||||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||||
| import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded | import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded | ||||||
| import bou.amine.apps.readerforselfossv2.utils.getImages | import bou.amine.apps.readerforselfossv2.utils.getImages | ||||||
| @@ -49,9 +48,6 @@ import org.kodein.di.DI | |||||||
| import org.kodein.di.DIAware | import org.kodein.di.DIAware | ||||||
| import org.kodein.di.android.x.closestDI | import org.kodein.di.android.x.closestDI | ||||||
| import org.kodein.di.instance | import org.kodein.di.instance | ||||||
| import retrofit2.Call |  | ||||||
| import retrofit2.Callback |  | ||||||
| import retrofit2.Response |  | ||||||
| import java.net.MalformedURLException | import java.net.MalformedURLException | ||||||
| import java.net.URL | import java.net.URL | ||||||
| import java.util.* | import java.util.* | ||||||
| @@ -81,6 +77,9 @@ class ArticleFragment : Fragment(), DIAware { | |||||||
|     private var font = "" |     private var font = "" | ||||||
|     private var staticBar = false |     private var staticBar = false | ||||||
|  |  | ||||||
|  |     private val mercuryApi : MercuryApi by instance() | ||||||
|  |  | ||||||
|  |  | ||||||
|     override fun onCreate(savedInstanceState: Bundle?) { |     override fun onCreate(savedInstanceState: Bundle?) { | ||||||
|         super.onCreate(savedInstanceState) |         super.onCreate(savedInstanceState) | ||||||
|  |  | ||||||
| @@ -249,88 +248,79 @@ class ArticleFragment : Fragment(), DIAware { | |||||||
|     private fun getContentFromMercury() { |     private fun getContentFromMercury() { | ||||||
|         if (repository.isNetworkAvailable()) { |         if (repository.isNetworkAvailable()) { | ||||||
|             binding.progressBar.visibility = View.VISIBLE |             binding.progressBar.visibility = View.VISIBLE | ||||||
|             val parser = MercuryApi() |  | ||||||
|  |  | ||||||
|             parser.parseUrl(url).enqueue( |             CoroutineScope(Dispatchers.Main).launch { | ||||||
|                 object : Callback<ParsedContent> { |                  val response = mercuryApi.query(url) | ||||||
|                     override fun onResponse( |                 if (response.success) { | ||||||
|                         call: Call<ParsedContent>, |                     try { | ||||||
|                         response: Response<ParsedContent> |                         if (response.data != null && response.data!!.content != null && !response.data!!.content.isNullOrEmpty()) { | ||||||
|                     ) { |                             try { | ||||||
|                         // TODO: clean all the following after finding the mercury content issue |                                 binding.titleView.text = response.data!!.title | ||||||
|                         try { |                                 if (typeface != null) { | ||||||
|                             if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) { |                                     binding.titleView.typeface = typeface | ||||||
|  |                                 } | ||||||
|                                 try { |                                 try { | ||||||
|                                     binding.titleView.text = response.body()!!.title |                                     // Note: Mercury may return relative urls... If it does the url val will not be changed. | ||||||
|                                     if (typeface != null) { |                                     URL(response.data!!.url) | ||||||
|                                         binding.titleView.typeface = typeface |                                     url = response.data!!.url | ||||||
|                                     } |                                 } catch (e: MalformedURLException) { | ||||||
|  |                                     // Mercury returned a relative url. We do nothing. | ||||||
|  |                                 } | ||||||
|  |                             } catch (e: Exception) { | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             try { | ||||||
|  |                                 contentText = response.data!!.content.orEmpty() | ||||||
|  |                                 htmlToWebview() | ||||||
|  |                             } catch (e: Exception) { | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             try { | ||||||
|  |                                 if (response.data!!.lead_image_url != null && !response.data!!.lead_image_url.isNullOrEmpty() && context != null) { | ||||||
|  |                                     binding.imageView.visibility = View.VISIBLE | ||||||
|                                     try { |                                     try { | ||||||
|                                         // Note: Mercury may return relative urls... If it does the url val will not be changed. |                                         Glide | ||||||
|                                         URL(response.body()!!.url) |                                             .with(requireContext()) | ||||||
|                                         url = response.body()!!.url |                                             .asBitmap() | ||||||
|                                     } catch (e: MalformedURLException) { |                                             .load( | ||||||
|                                         // Mercury returned a relative url. We do nothing. |                                                 response.data!!.lead_image_url.orEmpty() | ||||||
|  |                                             ) | ||||||
|  |                                             .apply(RequestOptions.fitCenterTransform()) | ||||||
|  |                                             .into(binding.imageView) | ||||||
|  |                                     } catch (e: Exception) { | ||||||
|                                     } |                                     } | ||||||
|                                 } catch (e: Exception) { |                                 } else { | ||||||
|  |                                     binding.imageView.visibility = View.GONE | ||||||
|                                 } |                                 } | ||||||
|  |                             } catch (e: Exception) { | ||||||
|                                 try { |                                 if (context != null) { | ||||||
|                                     contentText = response.body()!!.content.orEmpty() |  | ||||||
|                                     htmlToWebview() |  | ||||||
|                                 } catch (e: Exception) { |  | ||||||
|                                 } |  | ||||||
|  |  | ||||||
|                                 try { |  | ||||||
|                                     if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) { |  | ||||||
|                                         binding.imageView.visibility = View.VISIBLE |  | ||||||
|                                         try { |  | ||||||
|                                             Glide |  | ||||||
|                                                 .with(requireContext()) |  | ||||||
|                                                 .asBitmap() |  | ||||||
|                                                 .load( |  | ||||||
|                                                     response.body()!!.lead_image_url.orEmpty() |  | ||||||
|                                                 ) |  | ||||||
|                                                 .apply(RequestOptions.fitCenterTransform()) |  | ||||||
|                                                 .into(binding.imageView) |  | ||||||
|                                         } catch (e: Exception) { |  | ||||||
|                                         } |  | ||||||
|                                     } else { |  | ||||||
|                                         binding.imageView.visibility = View.GONE |  | ||||||
|                                     } |  | ||||||
|                                 } catch (e: Exception) { |  | ||||||
|                                     if (context != null) { |  | ||||||
|                                     } |  | ||||||
|                                 } |  | ||||||
|  |  | ||||||
|                                 try { |  | ||||||
|                                     binding.nestedScrollView.scrollTo(0, 0) |  | ||||||
|  |  | ||||||
|                                     binding.progressBar.visibility = View.GONE |  | ||||||
|                                 } catch (e: Exception) { |  | ||||||
|                                     if (context != null) { |  | ||||||
|                                     } |  | ||||||
|                                 } |  | ||||||
|                             } else { |  | ||||||
|                                 try { |  | ||||||
|                                     openInBrowserAfterFailing() |  | ||||||
|                                 } catch (e: Exception) { |  | ||||||
|                                     if (context != null) { |  | ||||||
|                                     } |  | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|                         } catch (e: Exception) { |  | ||||||
|                             if (context != null) { |                             try { | ||||||
|  |                                 binding.nestedScrollView.scrollTo(0, 0) | ||||||
|  |  | ||||||
|  |                                 binding.progressBar.visibility = View.GONE | ||||||
|  |                             } catch (e: Exception) { | ||||||
|  |                                 if (context != null) { | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } else { | ||||||
|  |                             try { | ||||||
|  |                                 openInBrowserAfterFailing() | ||||||
|  |                             } catch (e: Exception) { | ||||||
|  |                                 if (context != null) { | ||||||
|  |                                 } | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|  |                     } catch (e: Exception) { | ||||||
|  |                         if (context != null) { | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|  |                 } else { | ||||||
|                     override fun onFailure( |                     openInBrowserAfterFailing() | ||||||
|                         call: Call<ParsedContent>, |  | ||||||
|                         t: Throwable |  | ||||||
|                     ) = openInBrowserAfterFailing() |  | ||||||
|                 } |                 } | ||||||
|             ) |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ package bou.amine.apps.readerforselfossv2.android.model | |||||||
| import android.os.Parcel | import android.os.Parcel | ||||||
| import android.os.Parcelable | import android.os.Parcelable | ||||||
| import bou.amine.apps.readerforselfossv2.model.SelfossModel | import bou.amine.apps.readerforselfossv2.model.SelfossModel | ||||||
| import com.google.gson.annotations.SerializedName |  | ||||||
|  |  | ||||||
| fun SelfossModel.Item.toParcelable() : ParecelableItem = | fun SelfossModel.Item.toParcelable() : ParecelableItem = | ||||||
|     ParecelableItem( |     ParecelableItem( | ||||||
| @@ -34,17 +33,17 @@ fun ParecelableItem.toModel() : SelfossModel.Item = | |||||||
|         this.tags.split(",") |         this.tags.split(",") | ||||||
|     ) |     ) | ||||||
| data class ParecelableItem( | data class ParecelableItem( | ||||||
|     @SerializedName("id") val id: Int, |     val id: Int, | ||||||
|     @SerializedName("datetime") val datetime: String, |     val datetime: String, | ||||||
|     @SerializedName("title") val title: String, |     val title: String, | ||||||
|     @SerializedName("content") val content: String, |     val content: String, | ||||||
|     @SerializedName("unread") var unread: Boolean, |     var unread: Boolean, | ||||||
|     @SerializedName("starred") var starred: Boolean, |     var starred: Boolean, | ||||||
|     @SerializedName("thumbnail") val thumbnail: String?, |     val thumbnail: String?, | ||||||
|     @SerializedName("icon") val icon: String?, |     val icon: String?, | ||||||
|     @SerializedName("link") val link: String, |     val link: String, | ||||||
|     @SerializedName("sourcetitle") val sourcetitle: String, |     val sourcetitle: String, | ||||||
|     @SerializedName("tags") val tags: String |     val tags: String | ||||||
| ) : Parcelable { | ) : Parcelable { | ||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								androidApp/src/main/res/drawable/checkerboard.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								androidApp/src/main/res/drawable/checkerboard.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | <bitmap | ||||||
|  |     xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:dither="true" | ||||||
|  |     android:src="@drawable/checktile" | ||||||
|  |     android:tileMode="repeat"/> | ||||||
							
								
								
									
										
											BIN
										
									
								
								androidApp/src/main/res/drawable/checktile.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								androidApp/src/main/res/drawable/checktile.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 235 B | 
| @@ -1,5 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" |     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|     android:layout_width="match_parent" |     android:layout_width="match_parent" | ||||||
|     android:layout_height="match_parent"> |     android:layout_height="match_parent"> | ||||||
| @@ -9,8 +9,8 @@ | |||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|         android:layout_height="match_parent" |         android:layout_height="match_parent" | ||||||
|         android:layout_centerVertical="true" |         android:layout_centerVertical="true" | ||||||
|         android:layout_centerHorizontal="true" |         android:adjustViewBounds="true" | ||||||
|         android:background="@android:color/black" |         android:background="@drawable/checkerboard" | ||||||
|         app:srcCompat="@android:drawable/screen_background_dark" /> |         app:srcCompat="@android:drawable/screen_background_dark" /> | ||||||
|  |  | ||||||
| </androidx.coordinatorlayout.widget.CoordinatorLayout> | </RelativeLayout> | ||||||
| @@ -4,6 +4,8 @@ import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB | |||||||
| import bou.amine.apps.readerforselfossv2.dao.SOURCE | import bou.amine.apps.readerforselfossv2.dao.SOURCE | ||||||
| import bou.amine.apps.readerforselfossv2.dao.TAG | import bou.amine.apps.readerforselfossv2.dao.TAG | ||||||
| import bou.amine.apps.readerforselfossv2.model.SelfossModel | import bou.amine.apps.readerforselfossv2.model.SelfossModel | ||||||
|  | import bou.amine.apps.readerforselfossv2.model.StatusAndData | ||||||
|  | import bou.amine.apps.readerforselfossv2.model.SuccessResponse | ||||||
| import bou.amine.apps.readerforselfossv2.rest.SelfossApi | import bou.amine.apps.readerforselfossv2.rest.SelfossApi | ||||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||||
| import bou.amine.apps.readerforselfossv2.utils.ItemType | import bou.amine.apps.readerforselfossv2.utils.ItemType | ||||||
| @@ -47,11 +49,11 @@ class RepositoryTest { | |||||||
|         every { appSettingsService.isItemCachingEnabled() } returns false |         every { appSettingsService.isItemCachingEnabled() } returns false | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false |         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||||
|  |  | ||||||
|         coEvery { api.version() } returns SelfossModel.StatusAndData( |         coEvery { api.version() } returns StatusAndData( | ||||||
|             success = true, |             success = true, | ||||||
|             data = SelfossModel.ApiVersion("2.19-ba1e8e3", "4.0.0") |             data = SelfossModel.ApiVersion("2.19-ba1e8e3", "4.0.0") | ||||||
|         ) |         ) | ||||||
|         coEvery { api.stats() } returns SelfossModel.StatusAndData( |         coEvery { api.stats() } returns StatusAndData( | ||||||
|             success = true, |             success = true, | ||||||
|             data = SelfossModel.Stats(NUMBER_ARTICLES, NUMBER_UNREAD, NUMBER_STARRED) |             data = SelfossModel.Stats(NUMBER_ARTICLES, NUMBER_UNREAD, NUMBER_STARRED) | ||||||
|         ) |         ) | ||||||
| @@ -83,7 +85,7 @@ class RepositoryTest { | |||||||
|     fun get_api_4_date_with_api_1_version_stored() { |     fun get_api_4_date_with_api_1_version_stored() { | ||||||
|         every { appSettingsService.getApiVersion() } returns 1 |         every { appSettingsService.getApiVersion() } returns 1 | ||||||
|         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns |         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns | ||||||
|                 SelfossModel.StatusAndData(success = true, data = generateTestApiItem()) |                 StatusAndData(success = true, data = generateTestApiItem()) | ||||||
|         every { appSettingsService.updateApiVersion(any()) } returns Unit |         every { appSettingsService.updateApiVersion(any()) } returns Unit | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
| @@ -98,11 +100,11 @@ class RepositoryTest { | |||||||
|     @Test |     @Test | ||||||
|     fun get_api_1_date_with_api_4_version_stored() { |     fun get_api_1_date_with_api_4_version_stored() { | ||||||
|         every { appSettingsService.getApiVersion() } returns 4 |         every { appSettingsService.getApiVersion() } returns 4 | ||||||
|         coEvery { api.version() } returns SelfossModel.StatusAndData(success = false, null) |         coEvery { api.version() } returns StatusAndData(success = false, null) | ||||||
|         val itemParameters = FakeItemParameters() |         val itemParameters = FakeItemParameters() | ||||||
|         itemParameters.datetime = "2021-04-23 11:45:32" |         itemParameters.datetime = "2021-04-23 11:45:32" | ||||||
|         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns |         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns | ||||||
|                 SelfossModel.StatusAndData( |                 StatusAndData( | ||||||
|                     success = true, |                     success = true, | ||||||
|                     data = generateTestApiItem(itemParameters) |                     data = generateTestApiItem(itemParameters) | ||||||
|                 ) |                 ) | ||||||
| @@ -118,7 +120,7 @@ class RepositoryTest { | |||||||
|     @Test |     @Test | ||||||
|     fun get_newer_items() { |     fun get_newer_items() { | ||||||
|         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns |         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns | ||||||
|                 SelfossModel.StatusAndData(success = true, data = generateTestApiItem()) |                 StatusAndData(success = true, data = generateTestApiItem()) | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         runBlocking { |         runBlocking { | ||||||
| @@ -133,7 +135,7 @@ class RepositoryTest { | |||||||
|     @Test |     @Test | ||||||
|     fun get_all_newer_items() { |     fun get_all_newer_items() { | ||||||
|         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns |         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns | ||||||
|                 SelfossModel.StatusAndData(success = true, data = generateTestApiItem()) |                 StatusAndData(success = true, data = generateTestApiItem()) | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         repository.displayedItems = ItemType.ALL |         repository.displayedItems = ItemType.ALL | ||||||
| @@ -149,7 +151,7 @@ class RepositoryTest { | |||||||
|     @Test |     @Test | ||||||
|     fun get_newer_starred_items() { |     fun get_newer_starred_items() { | ||||||
|         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns |         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns | ||||||
|                 SelfossModel.StatusAndData(success = true, data = generateTestApiItem()) |                 StatusAndData(success = true, data = generateTestApiItem()) | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         repository.displayedItems = ItemType.STARRED |         repository.displayedItems = ItemType.STARRED | ||||||
| @@ -242,7 +244,7 @@ class RepositoryTest { | |||||||
|     @Test |     @Test | ||||||
|     fun get_older_items() { |     fun get_older_items() { | ||||||
|         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns |         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns | ||||||
|                 SelfossModel.StatusAndData(success = true, data = generateTestApiItem()) |                 StatusAndData(success = true, data = generateTestApiItem()) | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         repository.items = ArrayList(generateTestApiItem()) |         repository.items = ArrayList(generateTestApiItem()) | ||||||
| @@ -258,7 +260,7 @@ class RepositoryTest { | |||||||
|     @Test |     @Test | ||||||
|     fun get_all_older_items() { |     fun get_all_older_items() { | ||||||
|         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns |         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns | ||||||
|                 SelfossModel.StatusAndData(success = true, data = generateTestApiItem()) |                 StatusAndData(success = true, data = generateTestApiItem()) | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         repository.items = ArrayList(generateTestApiItem()) |         repository.items = ArrayList(generateTestApiItem()) | ||||||
| @@ -275,7 +277,7 @@ class RepositoryTest { | |||||||
|     @Test |     @Test | ||||||
|     fun get_older_starred_items() { |     fun get_older_starred_items() { | ||||||
|         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns |         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns | ||||||
|                 SelfossModel.StatusAndData(success = true, data = generateTestApiItem()) |                 StatusAndData(success = true, data = generateTestApiItem()) | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         repository.displayedItems = ItemType.STARRED |         repository.displayedItems = ItemType.STARRED | ||||||
| @@ -308,7 +310,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     fun reload_badges_without_response() { |     fun reload_badges_without_response() { | ||||||
|         coEvery { api.stats() } returns SelfossModel.StatusAndData(success = false, data = null) |         coEvery { api.stats() } returns StatusAndData(success = false, data = null) | ||||||
|  |  | ||||||
|         var success: Boolean |         var success: Boolean | ||||||
|  |  | ||||||
| @@ -376,7 +378,7 @@ class RepositoryTest { | |||||||
|             TAG("second_DB", "yellow", 0) |             TAG("second_DB", "yellow", 0) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         coEvery { api.tags() } returns SelfossModel.StatusAndData(success = true, data = tags) |         coEvery { api.tags() } returns StatusAndData(success = true, data = tags) | ||||||
|         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB |         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns true |         every { appSettingsService.isUpdateSourcesEnabled() } returns true | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns true |         every { appSettingsService.isItemCachingEnabled() } returns true | ||||||
| @@ -403,7 +405,7 @@ class RepositoryTest { | |||||||
|             TAG("second_DB", "yellow", 0) |             TAG("second_DB", "yellow", 0) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         coEvery { api.tags() } returns SelfossModel.StatusAndData(success = true, data = tags) |         coEvery { api.tags() } returns StatusAndData(success = true, data = tags) | ||||||
|         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB |         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false |         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns true |         every { appSettingsService.isItemCachingEnabled() } returns true | ||||||
| @@ -433,7 +435,7 @@ class RepositoryTest { | |||||||
|             TAG("second_DB", "yellow", 0) |             TAG("second_DB", "yellow", 0) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         coEvery { api.tags() } returns SelfossModel.StatusAndData(success = true, data = tags) |         coEvery { api.tags() } returns StatusAndData(success = true, data = tags) | ||||||
|         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB |         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns true |         every { appSettingsService.isUpdateSourcesEnabled() } returns true | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns false |         every { appSettingsService.isItemCachingEnabled() } returns false | ||||||
| @@ -460,7 +462,7 @@ class RepositoryTest { | |||||||
|             TAG("second_DB", "yellow", 0) |             TAG("second_DB", "yellow", 0) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         coEvery { api.tags() } returns SelfossModel.StatusAndData(success = true, data = tags) |         coEvery { api.tags() } returns StatusAndData(success = true, data = tags) | ||||||
|         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB |         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false |         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns false |         every { appSettingsService.isItemCachingEnabled() } returns false | ||||||
| @@ -489,7 +491,7 @@ class RepositoryTest { | |||||||
|             TAG("second_DB", "yellow", 0) |             TAG("second_DB", "yellow", 0) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         coEvery { api.tags() } returns SelfossModel.StatusAndData(success = true, data = tags) |         coEvery { api.tags() } returns StatusAndData(success = true, data = tags) | ||||||
|         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB |         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns true |         every { appSettingsService.isUpdateSourcesEnabled() } returns true | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns true |         every { appSettingsService.isItemCachingEnabled() } returns true | ||||||
| @@ -517,7 +519,7 @@ class RepositoryTest { | |||||||
|             TAG("second_DB", "yellow", 0) |             TAG("second_DB", "yellow", 0) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         coEvery { api.tags() } returns SelfossModel.StatusAndData(success = true, data = tags) |         coEvery { api.tags() } returns StatusAndData(success = true, data = tags) | ||||||
|         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB |         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns false |         every { appSettingsService.isItemCachingEnabled() } returns false | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns true |         every { appSettingsService.isUpdateSourcesEnabled() } returns true | ||||||
| @@ -544,7 +546,7 @@ class RepositoryTest { | |||||||
|             TAG("second_DB", "yellow", 0) |             TAG("second_DB", "yellow", 0) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         coEvery { api.tags() } returns SelfossModel.StatusAndData(success = true, data = tags) |         coEvery { api.tags() } returns StatusAndData(success = true, data = tags) | ||||||
|         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB |         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false |         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns true |         every { appSettingsService.isItemCachingEnabled() } returns true | ||||||
| @@ -572,7 +574,7 @@ class RepositoryTest { | |||||||
|             TAG("second_DB", "yellow", 0) |             TAG("second_DB", "yellow", 0) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         coEvery { api.tags() } returns SelfossModel.StatusAndData(success = true, data = tags) |         coEvery { api.tags() } returns StatusAndData(success = true, data = tags) | ||||||
|         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB |         coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false |         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns false |         every { appSettingsService.isItemCachingEnabled() } returns false | ||||||
| @@ -627,7 +629,7 @@ class RepositoryTest { | |||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources) |         coEvery { api.sources() } returns StatusAndData(success = true, data = sources) | ||||||
|         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB |         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source>? | ||||||
| @@ -681,7 +683,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false |         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns true |         every { appSettingsService.isItemCachingEnabled() } returns true | ||||||
|         coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources) |         coEvery { api.sources() } returns StatusAndData(success = true, data = sources) | ||||||
|         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB |         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source>? | ||||||
| @@ -738,7 +740,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns true |         every { appSettingsService.isUpdateSourcesEnabled() } returns true | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns false |         every { appSettingsService.isItemCachingEnabled() } returns false | ||||||
|         coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources) |         coEvery { api.sources() } returns StatusAndData(success = true, data = sources) | ||||||
|         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB |         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source>? | ||||||
| @@ -792,7 +794,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false |         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns false |         every { appSettingsService.isItemCachingEnabled() } returns false | ||||||
|         coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources) |         coEvery { api.sources() } returns StatusAndData(success = true, data = sources) | ||||||
|         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB |         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source>? | ||||||
| @@ -844,7 +846,7 @@ class RepositoryTest { | |||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources) |         coEvery { api.sources() } returns StatusAndData(success = true, data = sources) | ||||||
|         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB |         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB | ||||||
|         initializeRepository(MutableStateFlow(false)) |         initializeRepository(MutableStateFlow(false)) | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source>? | ||||||
| @@ -898,7 +900,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns false |         every { appSettingsService.isItemCachingEnabled() } returns false | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns true |         every { appSettingsService.isUpdateSourcesEnabled() } returns true | ||||||
|         coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources) |         coEvery { api.sources() } returns StatusAndData(success = true, data = sources) | ||||||
|         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB |         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB | ||||||
|         initializeRepository(MutableStateFlow(false)) |         initializeRepository(MutableStateFlow(false)) | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source>? | ||||||
| @@ -952,7 +954,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns true |         every { appSettingsService.isItemCachingEnabled() } returns true | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false |         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||||
|         coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources) |         coEvery { api.sources() } returns StatusAndData(success = true, data = sources) | ||||||
|         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB |         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB | ||||||
|         initializeRepository(MutableStateFlow(false)) |         initializeRepository(MutableStateFlow(false)) | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source>? | ||||||
| @@ -1006,7 +1008,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns false |         every { appSettingsService.isItemCachingEnabled() } returns false | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false |         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||||
|         coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources) |         coEvery { api.sources() } returns StatusAndData(success = true, data = sources) | ||||||
|         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB |         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB | ||||||
|         initializeRepository(MutableStateFlow(false)) |         initializeRepository(MutableStateFlow(false)) | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source>? | ||||||
| @@ -1022,7 +1024,7 @@ class RepositoryTest { | |||||||
|     @Test |     @Test | ||||||
|     fun create_source() { |     fun create_source() { | ||||||
|         coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns |         coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns | ||||||
|                 SelfossModel.SuccessResponse(true) |                 SuccessResponse(true) | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         var response: Boolean |         var response: Boolean | ||||||
| @@ -1052,7 +1054,7 @@ class RepositoryTest { | |||||||
|     @Test |     @Test | ||||||
|     fun create_source_but_response_fails() { |     fun create_source_but_response_fails() { | ||||||
|         coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns |         coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns | ||||||
|                 SelfossModel.SuccessResponse(false) |                 SuccessResponse(false) | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         var response: Boolean |         var response: Boolean | ||||||
| @@ -1082,7 +1084,7 @@ class RepositoryTest { | |||||||
|     @Test |     @Test | ||||||
|     fun create_source_without_connection() { |     fun create_source_without_connection() { | ||||||
|         coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns |         coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns | ||||||
|                 SelfossModel.SuccessResponse(true) |                 SuccessResponse(true) | ||||||
|  |  | ||||||
|         initializeRepository(MutableStateFlow(false)) |         initializeRepository(MutableStateFlow(false)) | ||||||
|         var response: Boolean |         var response: Boolean | ||||||
| @@ -1111,7 +1113,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     fun delete_source() { |     fun delete_source() { | ||||||
|         coEvery { api.deleteSource(any()) } returns SelfossModel.SuccessResponse(true) |         coEvery { api.deleteSource(any()) } returns SuccessResponse(true) | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         var response: Boolean |         var response: Boolean | ||||||
| @@ -1125,7 +1127,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     fun delete_source_but_response_fails() { |     fun delete_source_but_response_fails() { | ||||||
|         coEvery { api.deleteSource(any()) } returns SelfossModel.SuccessResponse(false) |         coEvery { api.deleteSource(any()) } returns SuccessResponse(false) | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         var response: Boolean |         var response: Boolean | ||||||
| @@ -1139,7 +1141,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     fun delete_source_without_connection() { |     fun delete_source_without_connection() { | ||||||
|         coEvery { api.deleteSource(any()) } returns SelfossModel.SuccessResponse(false) |         coEvery { api.deleteSource(any()) } returns SuccessResponse(false) | ||||||
|  |  | ||||||
|         initializeRepository(MutableStateFlow(false)) |         initializeRepository(MutableStateFlow(false)) | ||||||
|         var response: Boolean |         var response: Boolean | ||||||
| @@ -1153,7 +1155,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     fun update_remote() { |     fun update_remote() { | ||||||
|         coEvery { api.update() } returns SelfossModel.StatusAndData( |         coEvery { api.update() } returns StatusAndData( | ||||||
|             success = true, |             success = true, | ||||||
|             data = "finished" |             data = "finished" | ||||||
|         ) |         ) | ||||||
| @@ -1170,7 +1172,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     fun update_remote_but_response_fails() { |     fun update_remote_but_response_fails() { | ||||||
|         coEvery { api.update() } returns SelfossModel.StatusAndData( |         coEvery { api.update() } returns StatusAndData( | ||||||
|             success = false, |             success = false, | ||||||
|             data = "unallowed access" |             data = "unallowed access" | ||||||
|         ) |         ) | ||||||
| @@ -1187,7 +1189,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     fun update_remote_with_unallowed_access() { |     fun update_remote_with_unallowed_access() { | ||||||
|         coEvery { api.update() } returns SelfossModel.StatusAndData( |         coEvery { api.update() } returns StatusAndData( | ||||||
|             success = true, |             success = true, | ||||||
|             data = "unallowed access" |             data = "unallowed access" | ||||||
|         ) |         ) | ||||||
| @@ -1204,7 +1206,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     fun update_remote_without_connection() { |     fun update_remote_without_connection() { | ||||||
|         coEvery { api.update() } returns SelfossModel.StatusAndData( |         coEvery { api.update() } returns StatusAndData( | ||||||
|             success = true, |             success = true, | ||||||
|             data = "undocumented..." |             data = "undocumented..." | ||||||
|         ) |         ) | ||||||
| @@ -1221,7 +1223,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     fun login() { |     fun login() { | ||||||
|         coEvery { api.login() } returns SelfossModel.SuccessResponse(success = true) |         coEvery { api.login() } returns SuccessResponse(success = true) | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         var response: Boolean |         var response: Boolean | ||||||
| @@ -1235,7 +1237,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     fun login_but_response_fails() { |     fun login_but_response_fails() { | ||||||
|         coEvery { api.login() } returns SelfossModel.SuccessResponse(success = false) |         coEvery { api.login() } returns SuccessResponse(success = false) | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         var response: Boolean |         var response: Boolean | ||||||
| @@ -1249,7 +1251,7 @@ class RepositoryTest { | |||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     fun login_but_without_connection() { |     fun login_but_without_connection() { | ||||||
|         coEvery { api.login() } returns SelfossModel.SuccessResponse(success = true) |         coEvery { api.login() } returns SuccessResponse(success = true) | ||||||
|  |  | ||||||
|         initializeRepository(MutableStateFlow(false)) |         initializeRepository(MutableStateFlow(false)) | ||||||
|         var response: Boolean |         var response: Boolean | ||||||
| @@ -1297,9 +1299,9 @@ class RepositoryTest { | |||||||
|                 any() |                 any() | ||||||
|             ) |             ) | ||||||
|         } returnsMany listOf( |         } returnsMany listOf( | ||||||
|             SelfossModel.StatusAndData(success = true, data = generateTestApiItem(itemParameter1)), |             StatusAndData(success = true, data = generateTestApiItem(itemParameter1)), | ||||||
|             SelfossModel.StatusAndData(success = true, data = generateTestApiItem(itemParameter2)), |             StatusAndData(success = true, data = generateTestApiItem(itemParameter2)), | ||||||
|             SelfossModel.StatusAndData(success = true, data = generateTestApiItem(itemParameter1)), |             StatusAndData(success = true, data = generateTestApiItem(itemParameter1)), | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
| @@ -1323,7 +1325,7 @@ class RepositoryTest { | |||||||
|     @Test |     @Test | ||||||
|     fun cache_items_but_response_fails() { |     fun cache_items_but_response_fails() { | ||||||
|         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns |         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns | ||||||
|                 SelfossModel.StatusAndData(success = false, data = generateTestApiItem()) |                 StatusAndData(success = false, data = generateTestApiItem()) | ||||||
|  |  | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         repository.tagFilter = SelfossModel.Tag("Tag", "read", 0) |         repository.tagFilter = SelfossModel.Tag("Tag", "read", 0) | ||||||
| @@ -1346,7 +1348,7 @@ class RepositoryTest { | |||||||
|     @Test |     @Test | ||||||
|     fun cache_items_without_connection() { |     fun cache_items_without_connection() { | ||||||
|         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns |         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns | ||||||
|                 SelfossModel.StatusAndData(success = false, data = generateTestApiItem()) |                 StatusAndData(success = false, data = generateTestApiItem()) | ||||||
|  |  | ||||||
|         initializeRepository(MutableStateFlow(false)) |         initializeRepository(MutableStateFlow(false)) | ||||||
|         repository.tagFilter = SelfossModel.Tag("Tag", "read", 0) |         repository.tagFilter = SelfossModel.Tag("Tag", "read", 0) | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| package bou.amine.apps.readerforselfossv2.DI | package bou.amine.apps.readerforselfossv2.DI | ||||||
|  |  | ||||||
|  | import bou.amine.apps.readerforselfossv2.rest.MercuryApi | ||||||
| import bou.amine.apps.readerforselfossv2.rest.SelfossApi | import bou.amine.apps.readerforselfossv2.rest.SelfossApi | ||||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||||
| import org.kodein.di.DI | import org.kodein.di.DI | ||||||
| @@ -10,4 +11,5 @@ import org.kodein.di.singleton | |||||||
| val networkModule by DI.Module { | val networkModule by DI.Module { | ||||||
|     bind<AppSettingsService>() with singleton { AppSettingsService() } |     bind<AppSettingsService>() with singleton { AppSettingsService() } | ||||||
|     bind<SelfossApi>() with singleton { SelfossApi(instance()) } |     bind<SelfossApi>() with singleton { SelfossApi(instance()) } | ||||||
|  |     bind<MercuryApi>() with singleton { MercuryApi() } | ||||||
| } | } | ||||||
| @@ -0,0 +1,157 @@ | |||||||
|  | package bou.amine.apps.readerforselfossv2.model | ||||||
|  |  | ||||||
|  | import bou.amine.apps.readerforselfossv2.utils.DateUtils | ||||||
|  | import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded | ||||||
|  | import kotlinx.serialization.KSerializer | ||||||
|  | import kotlinx.serialization.Serializable | ||||||
|  | import kotlinx.serialization.descriptors.PrimitiveKind | ||||||
|  | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor | ||||||
|  | import kotlinx.serialization.descriptors.SerialDescriptor | ||||||
|  | import kotlinx.serialization.encoding.Decoder | ||||||
|  | import kotlinx.serialization.encoding.Encoder | ||||||
|  | import kotlinx.serialization.json.* | ||||||
|  |  | ||||||
|  | class MercuryModel { | ||||||
|  |  | ||||||
|  |     @Serializable | ||||||
|  |     class ParsedContent( | ||||||
|  |         val title: String, | ||||||
|  |         val content: String?, | ||||||
|  |         val lead_image_url: String?, | ||||||
|  |         val url: String | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     @Serializable | ||||||
|  |     data class Tag( | ||||||
|  |         val tag: String, | ||||||
|  |         val color: String, | ||||||
|  |         val unread: Int | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     @Serializable | ||||||
|  |     class Stats( | ||||||
|  |         val total: Int, | ||||||
|  |         val unread: Int, | ||||||
|  |         val starred: Int | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     @Serializable | ||||||
|  |     data class Spout( | ||||||
|  |         val name: String, | ||||||
|  |         val description: String | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     @Serializable | ||||||
|  |     data class ApiVersion( | ||||||
|  |         val version: String?, | ||||||
|  |         val apiversion: String? | ||||||
|  |     ) { | ||||||
|  |         fun getApiMajorVersion() : Int { | ||||||
|  |             var versionNumber = 0 | ||||||
|  |             if (apiversion != null) { | ||||||
|  |                 versionNumber = apiversion.substringBefore(".").toInt() | ||||||
|  |             } | ||||||
|  |             return versionNumber | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Serializable | ||||||
|  |     data class Source( | ||||||
|  |         val id: Int, | ||||||
|  |         val title: String, | ||||||
|  |         @Serializable(with = TagsListSerializer::class) | ||||||
|  |         val tags: List<String>, | ||||||
|  |         val spout: String, | ||||||
|  |         val error: String, | ||||||
|  |         val icon: String? | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     @Serializable | ||||||
|  |     data class Item( | ||||||
|  |         val id: Int, | ||||||
|  |         val datetime: String, | ||||||
|  |         val title: String, | ||||||
|  |         val content: String, | ||||||
|  |         @Serializable(with = BooleanSerializer::class) | ||||||
|  |         var unread: Boolean, | ||||||
|  |         @Serializable(with = BooleanSerializer::class) | ||||||
|  |         var starred: Boolean, | ||||||
|  |         val thumbnail: String?, | ||||||
|  |         val icon: String?, | ||||||
|  |         val link: String, | ||||||
|  |         val sourcetitle: String, | ||||||
|  |         @Serializable(with = TagsListSerializer::class) | ||||||
|  |         val tags: List<String> | ||||||
|  |     ) { | ||||||
|  |         // TODO: maybe find a better way to handle these kind of urls | ||||||
|  |         fun getLinkDecoded(): String { | ||||||
|  |             var stringUrl: String | ||||||
|  |             stringUrl = | ||||||
|  |                 if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) { | ||||||
|  |                     if (link.contains("&url=")) { | ||||||
|  |                         link.substringAfter("&url=") | ||||||
|  |                     } else { | ||||||
|  |                         this.link.replace("&", "&") | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     this.link.replace("&", "&") | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |             // handle :443 => https | ||||||
|  |             if (stringUrl.contains(":443")) { | ||||||
|  |                 stringUrl = stringUrl.replace(":443", "").replace("http://", "https://") | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // handle url not starting with http | ||||||
|  |             if (stringUrl.startsWith("//")) { | ||||||
|  |                 stringUrl = "http:$stringUrl" | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return stringUrl | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun sourceAndDateText(): String = | ||||||
|  |             this.sourcetitle.getHtmlDecoded() + DateUtils.parseRelativeDate(this.datetime) | ||||||
|  |  | ||||||
|  |         fun toggleStar(): Item { | ||||||
|  |             this.starred = !this.starred | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // TODO: this seems to be super slow. | ||||||
|  |     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() } | ||||||
|  |                 else -> json.toString().split(",") | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         override val descriptor: SerialDescriptor | ||||||
|  |             get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING) | ||||||
|  |  | ||||||
|  |         override fun serialize(encoder: Encoder, value: List<String>) { | ||||||
|  |             TODO("Not yet implemented") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     object BooleanSerializer : KSerializer<Boolean> { | ||||||
|  |         override fun deserialize(decoder: Decoder): Boolean { | ||||||
|  |             val json = ((decoder as JsonDecoder).decodeJsonElement()).jsonPrimitive | ||||||
|  |             return if (json.booleanOrNull != null) { | ||||||
|  |                 json.boolean | ||||||
|  |             } else { | ||||||
|  |                 json.int == 1 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         override val descriptor: SerialDescriptor | ||||||
|  |             get() = PrimitiveSerialDescriptor("b", PrimitiveKind.BOOLEAN) | ||||||
|  |  | ||||||
|  |         override fun serialize(encoder: Encoder, value: Boolean) { | ||||||
|  |             TODO("Not yet implemented") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | package bou.amine.apps.readerforselfossv2.model | ||||||
|  |  | ||||||
|  | import io.ktor.client.call.* | ||||||
|  | import io.ktor.client.statement.* | ||||||
|  | import io.ktor.http.* | ||||||
|  | import kotlinx.serialization.Serializable | ||||||
|  |  | ||||||
|  | @Serializable | ||||||
|  | class SuccessResponse(val success: Boolean) { | ||||||
|  |     val isSuccess: Boolean | ||||||
|  |         get() = success | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class StatusAndData<T>(val success: Boolean, val data: T? = null) { | ||||||
|  |     companion object { | ||||||
|  |         fun <T> succes(d: T): StatusAndData<T> { | ||||||
|  |             return StatusAndData(true, d) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun <T> error(): StatusAndData<T> { | ||||||
|  |             return StatusAndData(false) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | suspend fun maybeResponse(r: HttpResponse): SuccessResponse { | ||||||
|  |     return if (r.status.isSuccess()) { | ||||||
|  |         r.body() | ||||||
|  |     } else { | ||||||
|  |         SuccessResponse(false) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | suspend inline fun <reified T> bodyOrFailure(r: HttpResponse): StatusAndData<T> { | ||||||
|  |     return if (r.status.isSuccess()) { | ||||||
|  |         StatusAndData.succes(r.body()) | ||||||
|  |     } else { | ||||||
|  |         StatusAndData.error() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -20,12 +20,6 @@ class SelfossModel { | |||||||
|         val unread: Int |         val unread: Int | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     @Serializable |  | ||||||
|     class SuccessResponse(val success: Boolean) { |  | ||||||
|         val isSuccess: Boolean |  | ||||||
|             get() = success |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Serializable |     @Serializable | ||||||
|     class Stats( |     class Stats( | ||||||
|         val total: Int, |         val total: Int, | ||||||
| @@ -152,16 +146,4 @@ class SelfossModel { | |||||||
|             TODO("Not yet implemented") |             TODO("Not yet implemented") | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     class StatusAndData<T>(val success: Boolean, val data: T? = null) { |  | ||||||
|         companion object { |  | ||||||
|             fun <T> succes(d: T): StatusAndData<T> { |  | ||||||
|                 return StatusAndData(true, d) |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             fun <T> error(): StatusAndData<T> { |  | ||||||
|                 return StatusAndData(false) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package bou.amine.apps.readerforselfossv2.repository | |||||||
| import bou.amine.apps.readerforselfossv2.dao.* | import bou.amine.apps.readerforselfossv2.dao.* | ||||||
| import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException | import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException | ||||||
| import bou.amine.apps.readerforselfossv2.model.SelfossModel | import bou.amine.apps.readerforselfossv2.model.SelfossModel | ||||||
|  | import bou.amine.apps.readerforselfossv2.model.StatusAndData | ||||||
| import bou.amine.apps.readerforselfossv2.rest.SelfossApi | import bou.amine.apps.readerforselfossv2.rest.SelfossApi | ||||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||||
| import bou.amine.apps.readerforselfossv2.utils.* | import bou.amine.apps.readerforselfossv2.utils.* | ||||||
| @@ -40,7 +41,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | |||||||
|  |  | ||||||
|     suspend fun getNewerItems(): ArrayList<SelfossModel.Item> { |     suspend fun getNewerItems(): ArrayList<SelfossModel.Item> { | ||||||
|         // TODO: Use the updatedSince parameter |         // TODO: Use the updatedSince parameter | ||||||
|         var fetchedItems: SelfossModel.StatusAndData<List<SelfossModel.Item>> = SelfossModel.StatusAndData.error() |         var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error() | ||||||
|         var fromDB = false |         var fromDB = false | ||||||
|         if (isNetworkAvailable()) { |         if (isNetworkAvailable()) { | ||||||
|             fetchedItems = api.getItems( |             fetchedItems = api.getItems( | ||||||
| @@ -65,7 +66,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | |||||||
|                 if (sourceFilter != null) { |                 if (sourceFilter != null) { | ||||||
|                     dbItems = dbItems.filter { it.sourcetitle == sourceFilter!!.title } |                     dbItems = dbItems.filter { it.sourcetitle == sourceFilter!!.title } | ||||||
|                 } |                 } | ||||||
|                 fetchedItems = SelfossModel.StatusAndData.succes( |                 fetchedItems = StatusAndData.succes( | ||||||
|                     dbItems.map { it.toView() } |                     dbItems.map { it.toView() } | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
| @@ -81,7 +82,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     suspend fun getOlderItems(): ArrayList<SelfossModel.Item> { |     suspend fun getOlderItems(): ArrayList<SelfossModel.Item> { | ||||||
|         var fetchedItems: SelfossModel.StatusAndData<List<SelfossModel.Item>> = SelfossModel.StatusAndData.error() |         var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error() | ||||||
|         if (isNetworkAvailable()) { |         if (isNetworkAvailable()) { | ||||||
|             val offset = items.size |             val offset = items.size | ||||||
|             fetchedItems = api.getItems( |             fetchedItems = api.getItems( | ||||||
|   | |||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | package bou.amine.apps.readerforselfossv2.rest | ||||||
|  |  | ||||||
|  | import bou.amine.apps.readerforselfossv2.model.* | ||||||
|  | import io.github.aakira.napier.Napier | ||||||
|  | import io.ktor.client.* | ||||||
|  | import io.ktor.client.plugins.cache.* | ||||||
|  | import io.ktor.client.plugins.contentnegotiation.* | ||||||
|  | import io.ktor.client.plugins.logging.* | ||||||
|  | import io.ktor.client.request.* | ||||||
|  | import io.ktor.serialization.kotlinx.json.* | ||||||
|  | import kotlinx.serialization.json.Json | ||||||
|  |  | ||||||
|  | class MercuryApi() { | ||||||
|  |  | ||||||
|  |     var client = createHttpClient() | ||||||
|  |  | ||||||
|  |     private fun createHttpClient(): HttpClient { | ||||||
|  |         return HttpClient { | ||||||
|  |             install(ContentNegotiation) { | ||||||
|  |                 install(HttpCache) | ||||||
|  |                 json(Json { | ||||||
|  |                     prettyPrint = true | ||||||
|  |                     isLenient = true | ||||||
|  |                     ignoreUnknownKeys = true | ||||||
|  |                 }) | ||||||
|  |             } | ||||||
|  |             install(Logging) { | ||||||
|  |                 logger = object : Logger { | ||||||
|  |                     override fun log(message: String) { | ||||||
|  |                         Napier.d(message, tag = "LogMercuryCalls") | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 level = LogLevel.INFO | ||||||
|  |             } | ||||||
|  |             expectSuccess = false | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     suspend fun query(url: String): StatusAndData<MercuryModel.ParsedContent> = | ||||||
|  |         bodyOrFailure(client.get("https://amine-louveau.fr/parser.php") { | ||||||
|  |             parameter("link", url) | ||||||
|  |         }) | ||||||
|  | } | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| package bou.amine.apps.readerforselfossv2.rest | package bou.amine.apps.readerforselfossv2.rest | ||||||
|  |  | ||||||
| import bou.amine.apps.readerforselfossv2.model.SelfossModel | import bou.amine.apps.readerforselfossv2.model.* | ||||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||||
| import io.ktor.client.* | import io.ktor.client.* | ||||||
| import io.ktor.client.call.* | import io.ktor.client.call.* | ||||||
| @@ -66,7 +66,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|         client = createHttpClient() |         client = createHttpClient() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     suspend fun login(): SelfossModel.SuccessResponse = |     suspend fun login(): SuccessResponse = | ||||||
|         maybeResponse(client.get(url("/login")) { |         maybeResponse(client.get(url("/login")) { | ||||||
|             parameter("username", appSettingsService.getUserName()) |             parameter("username", appSettingsService.getUserName()) | ||||||
|             parameter("password", appSettingsService.getPassword()) |             parameter("password", appSettingsService.getPassword()) | ||||||
| @@ -80,7 +80,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|         search: String?, |         search: String?, | ||||||
|         updatedSince: String?, |         updatedSince: String?, | ||||||
|         items: Int? = null |         items: Int? = null | ||||||
|     ): SelfossModel.StatusAndData<List<SelfossModel.Item>> = |     ): StatusAndData<List<SelfossModel.Item>> = | ||||||
|         bodyOrFailure(client.get(url("/items")) { |         bodyOrFailure(client.get(url("/items")) { | ||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
| @@ -93,64 +93,64 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                 parameter("offset", offset) |                 parameter("offset", offset) | ||||||
|             }) |             }) | ||||||
|  |  | ||||||
|     suspend fun stats(): SelfossModel.StatusAndData<SelfossModel.Stats> = |     suspend fun stats(): StatusAndData<SelfossModel.Stats> = | ||||||
|         bodyOrFailure(client.get(url("/stats")) { |         bodyOrFailure(client.get(url("/stats")) { | ||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             }) |             }) | ||||||
|  |  | ||||||
|     suspend fun tags(): SelfossModel.StatusAndData<List<SelfossModel.Tag>> = |     suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> = | ||||||
|         bodyOrFailure(client.get(url("/tags")) { |         bodyOrFailure(client.get(url("/tags")) { | ||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             }) |             }) | ||||||
|  |  | ||||||
|     suspend fun update(): SelfossModel.StatusAndData<String> = |     suspend fun update(): StatusAndData<String> = | ||||||
|         bodyOrFailure(client.get(url("/update")) { |         bodyOrFailure(client.get(url("/update")) { | ||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             }) |             }) | ||||||
|  |  | ||||||
|     suspend fun spouts(): SelfossModel.StatusAndData<Map<String, SelfossModel.Spout>> = |     suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> = | ||||||
|         bodyOrFailure(client.get(url("/sources/spouts")) { |         bodyOrFailure(client.get(url("/sources/spouts")) { | ||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             }) |             }) | ||||||
|  |  | ||||||
|     suspend fun sources(): SelfossModel.StatusAndData<ArrayList<SelfossModel.Source>> = |     suspend fun sources(): StatusAndData<ArrayList<SelfossModel.Source>> = | ||||||
|         bodyOrFailure(client.get(url("/sources/list")) { |         bodyOrFailure(client.get(url("/sources/list")) { | ||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             }) |             }) | ||||||
|  |  | ||||||
|     suspend fun version(): SelfossModel.StatusAndData<SelfossModel.ApiVersion> = |     suspend fun version(): StatusAndData<SelfossModel.ApiVersion> = | ||||||
|         bodyOrFailure(client.get(url("/api/about"))) |         bodyOrFailure(client.get(url("/api/about"))) | ||||||
|  |  | ||||||
|     suspend fun markAsRead(id: String): SelfossModel.SuccessResponse = |     suspend fun markAsRead(id: String): SuccessResponse = | ||||||
|         maybeResponse(client.post(url("/mark/$id")) { |         maybeResponse(client.post(url("/mark/$id")) { | ||||||
|             parameter("username", appSettingsService.getUserName()) |             parameter("username", appSettingsService.getUserName()) | ||||||
|             parameter("password", appSettingsService.getPassword()) |             parameter("password", appSettingsService.getPassword()) | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse = |     suspend fun unmarkAsRead(id: String): SuccessResponse = | ||||||
|         maybeResponse(client.post(url("/unmark/$id")) { |         maybeResponse(client.post(url("/unmark/$id")) { | ||||||
|             parameter("username", appSettingsService.getUserName()) |             parameter("username", appSettingsService.getUserName()) | ||||||
|             parameter("password", appSettingsService.getPassword()) |             parameter("password", appSettingsService.getPassword()) | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun starr(id: String): SelfossModel.SuccessResponse = |     suspend fun starr(id: String): SuccessResponse = | ||||||
|         maybeResponse(client.post(url("/starr/$id")) { |         maybeResponse(client.post(url("/starr/$id")) { | ||||||
|             parameter("username", appSettingsService.getUserName()) |             parameter("username", appSettingsService.getUserName()) | ||||||
|             parameter("password", appSettingsService.getPassword()) |             parameter("password", appSettingsService.getPassword()) | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun unstarr(id: String): SelfossModel.SuccessResponse = |     suspend fun unstarr(id: String): SuccessResponse = | ||||||
|         maybeResponse(client.post(url("/unstarr/$id")) { |         maybeResponse(client.post(url("/unstarr/$id")) { | ||||||
|             parameter("username", appSettingsService.getUserName()) |             parameter("username", appSettingsService.getUserName()) | ||||||
|             parameter("password", appSettingsService.getPassword()) |             parameter("password", appSettingsService.getPassword()) | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun markAllAsRead(ids: List<String>): SelfossModel.SuccessResponse = |     suspend fun markAllAsRead(ids: List<String>): SuccessResponse = | ||||||
|         maybeResponse(client.submitForm( |         maybeResponse(client.submitForm( | ||||||
|             url = url("/mark"), |             url = url("/mark"), | ||||||
|             formParameters = Parameters.build { |             formParameters = Parameters.build { | ||||||
| @@ -167,7 +167,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|         tags: String, |         tags: String, | ||||||
|         filter: String, |         filter: String, | ||||||
|         version: Int |         version: Int | ||||||
|     ): SelfossModel.SuccessResponse = |     ): SuccessResponse = | ||||||
|         maybeResponse( |         maybeResponse( | ||||||
|             if (version > 1) { |             if (version > 1) { | ||||||
|                 createSource2(title, url, spout, tags, filter) |                 createSource2(title, url, spout, tags, filter) | ||||||
| @@ -212,25 +212,9 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     suspend fun deleteSource(id: Int): SelfossModel.SuccessResponse = |     suspend fun deleteSource(id: Int): SuccessResponse = | ||||||
|         maybeResponse(client.delete(url("/source/$id")) { |         maybeResponse(client.delete(url("/source/$id")) { | ||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             }) |             }) | ||||||
|  |  | ||||||
|     suspend fun maybeResponse(r: HttpResponse): SelfossModel.SuccessResponse { |  | ||||||
|         return if (r.status.isSuccess()) { |  | ||||||
|             r.body() |  | ||||||
|         } else { |  | ||||||
|             SelfossModel.SuccessResponse(false) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     suspend inline fun <reified T> bodyOrFailure(r: HttpResponse): SelfossModel.StatusAndData<T> { |  | ||||||
|         return if (r.status.isSuccess()) { |  | ||||||
|             SelfossModel.StatusAndData.succes(r.body()) |  | ||||||
|         } else { |  | ||||||
|             SelfossModel.StatusAndData.error() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
		Reference in New Issue
	
	Block a user