diff --git a/.gitea/workflows/on_pr.yml b/.gitea/workflows/on_pr.yml index 6451b21..183053d 100644 --- a/.gitea/workflows/on_pr.yml +++ b/.gitea/workflows/on_pr.yml @@ -16,13 +16,13 @@ jobs: java-version: '17' cache: gradle - name: Install klint - run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.0.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/ + run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.5.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/ - name: Install detekt - run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.1/detekt-cli-1.23.1.zip && unzip detekt-cli-1.23.1.zip + run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.7/detekt-cli-1.23.7.zip && unzip detekt-cli-1.23.7.zip - name: Linting... run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build' - name: Detecting... - run: ./detekt-cli-1.23.1/bin/detekt-cli --all-rules --excludes '**/shared/build/**/*.kt' || true + run: ./detekt-cli-1.23.7/bin/detekt-cli -c detekt.yml --excludes '**/shared/build/**/*.kt' build: needs: Lint uses: ./.gitea/workflows/common_build.yml diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/Helpers.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/Helpers.kt index 3895a04..207afcf 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/Helpers.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/Helpers.kt @@ -30,21 +30,16 @@ fun withError( ): TypeSafeMatcher { return object : TypeSafeMatcher() { override fun matchesSafely(view: View?): Boolean { - if (view == null) { - return false - } - val context = view.context - if (view !is EditText) { - return false - } - if (view.error == null) { + if (view != null && (view !is EditText || view.error == null)) { return false } + val context = view!!.context - return view.error.toString() == context.getString(id) + return (view as EditText).error.toString() == context.getString(id) } override fun describeTo(description: Description?) { + // Nothing } } } @@ -58,6 +53,7 @@ fun withDrawable( description.appendText("ImageView with drawable same as drawable with id $id") } + @Suppress("detekt:SwallowedException") override fun matchesSafely(view: View): Boolean { val context = view.context val expectedBitmap = context.getDrawable(id)!!.toBitmap() diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/LoginActivityTest.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/LoginActivityTest.kt index 4ebe150..817a7d0 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/LoginActivityTest.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/LoginActivityTest.kt @@ -1,6 +1,5 @@ package bou.amine.apps.readerforselfossv2.android -import android.app.Activity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.action.ViewActions.click @@ -26,14 +25,6 @@ class LoginActivityTest { @get:Rule val activityRule = ActivityScenarioRule(LoginActivity::class.java) - private fun getActivity(): Activity? { - var activity: Activity? = null - activityRule.scenario.onActivity { - activity = it - } - return activity - } - @Before fun registerIdlingResource() { IdlingRegistry diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityGeneralTest.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityGeneralTest.kt index b457f44..80463c2 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityGeneralTest.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityGeneralTest.kt @@ -42,6 +42,7 @@ class SettingsActivityGeneralTest { onView(withText(R.string.pref_header_general)).perform(click()) } + @Suppress("detekt:LongMethod") @Test fun testGeneral() { onView(withText(R.string.pref_api_items_number_title)).check(matches(isDisplayed())) @@ -118,6 +119,7 @@ class SettingsActivityGeneralTest { ) } + @Suppress("detekt:ForbiddenComment") @Test fun testGeneralActionsNumberItems() { onView(withText(R.string.pref_api_items_number_title)).perform(click()) diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityOfflineTest.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityOfflineTest.kt index 26df578..729901c 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityOfflineTest.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityOfflineTest.kt @@ -42,6 +42,7 @@ class SettingsActivityOfflineTest { onView(withText(R.string.pref_header_offline)).perform(click()) } + @Suppress("detekt:LongMethod") @Test fun testOffline() { onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).check( @@ -107,6 +108,7 @@ class SettingsActivityOfflineTest { ) } + @Suppress("detekt:LongMethod") @Test fun testOfflineActions() { onView(withText(R.string.pref_switch_items_caching_off)).check(matches(isDisplayed())) diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SourcesActivityTest.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SourcesActivityTest.kt index 53cf67d..403b7b1 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SourcesActivityTest.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SourcesActivityTest.kt @@ -45,6 +45,7 @@ class SourcesActivityTest { ) } + @Suppress("detekt:SwallowedException") @Test fun addSourceCheckContent() { testAddSourceWithUrl("https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en", sourceName) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt index cc61c85..5217411 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt @@ -49,6 +49,8 @@ import org.kodein.di.instance import java.security.MessageDigest import java.util.concurrent.TimeUnit +private const val MIN_WIDTH_CARD_DP = 300 + class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, @@ -200,6 +202,7 @@ class HomeActivity : } } + @Suppress("detekt:LongMethod") private fun handleBottomBar() { tabNewBadge = TextBadgeItem() @@ -282,7 +285,7 @@ class HomeActivity : handleBottomBarActions() - handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false)) + handleGdprDialog(appSettingsService.settings.getBoolean("GDPR_shown", false)) handleRecurringTask() CountingIdlingResourceSingleton.increment() @@ -294,10 +297,10 @@ class HomeActivity : getElementsAccordingToTab() } - private fun handleGDPRDialog(GDPRShown: Boolean) { + private fun handleGdprDialog(gdprShown: Boolean) { val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256") messageDigest.update(appSettingsService.getBaseUrl().toByteArray()) - if (!GDPRShown) { + if (!gdprShown) { val alertDialog = AlertDialog.Builder(this).create() alertDialog.setTitle(getString(R.string.gdpr_dialog_title)) alertDialog.setMessage(getString(R.string.gdpr_dialog_message)) @@ -543,7 +546,7 @@ class HomeActivity : private fun calculateNoOfColumns(): Int { val displayMetrics = resources.displayMetrics val dpWidth = displayMetrics.widthPixels / displayMetrics.density - return (dpWidth / 300).toInt() + return (dpWidth / MIN_WIDTH_CARD_DP).toInt() } override fun onQueryTextChange(p0: String?): Boolean { @@ -592,6 +595,7 @@ class HomeActivity : .show() } + @Suppress("detekt:ReturnCount", "detekt:LongMethod") override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.issue_tracker -> { diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt index 29f1234..ac3a41f 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt @@ -30,6 +30,8 @@ import org.kodein.di.DIAware import org.kodein.di.android.closestDI import org.kodein.di.instance +private const val MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED = 3 + class LoginActivity : AppCompatActivity(), DIAware { @@ -217,7 +219,7 @@ class LoginActivity : cancel = true binding.urlView.error = getString(R.string.login_url_problem) inValidCount++ - if (inValidCount == 3) { + if (inValidCount == MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED) { val alertDialog = AlertDialog.Builder(this).create() alertDialog.setTitle(getString(R.string.warning_wrong_url)) alertDialog.setMessage(getString(R.string.text_wrong_url)) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt index c5c123d..43d0879 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt @@ -62,6 +62,7 @@ class MyApp : private val connectivityStatus: ConnectivityStatus by instance() private val driverFactory: DriverFactory by instance() + @Suppress("detekt:ForbiddenComment") // TODO: handle with the "previous" way private val isConnectionAvailable: MutableStateFlow = MutableStateFlow(true) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ReaderActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ReaderActivity.kt index c5a4bbc..055b974 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ReaderActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ReaderActivity.kt @@ -53,6 +53,7 @@ class ReaderActivity : showMenuItem(false) } + @Suppress("detekt:SwallowedException") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityReaderBinding.inflate(layoutInflater) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/UpsertSourceActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/UpsertSourceActivity.kt index 98c27e5..548aa6f 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/UpsertSourceActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/UpsertSourceActivity.kt @@ -85,6 +85,7 @@ class UpsertSourceActivity : } } + @Suppress("detekt:SwallowedException") private fun handleSpoutsSpinner() { val spoutsKV = HashMap() binding.spoutsSpinner.onItemSelectedListener = @@ -173,6 +174,7 @@ class UpsertSourceActivity : sourceDetailsUnavailable -> { Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show() } + else -> { CoroutineScope(Dispatchers.Main).launch { val successfullyAddedSource = diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/LoadingWorker.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/LoadingWorker.kt index 955ee80..0d73f83 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/LoadingWorker.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/LoadingWorker.kt @@ -26,6 +26,8 @@ import org.kodein.di.instance import java.util.Timer import kotlin.concurrent.schedule +private const val NOTIFICATION_DELAY = 4000L + class LoadingWorker( val context: Context, params: WorkerParameters, @@ -106,11 +108,11 @@ class LoadingWorker( .setAutoCancel(true) .setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp) - Timer("", false).schedule(4000) { + Timer("", false).schedule(NOTIFICATION_DELAY) { notificationManager.notify(2, newItemsNotification.build()) } } - Timer("", false).schedule(4000) { + Timer("", false).schedule(NOTIFICATION_DELAY) { notificationManager.cancel(1) } } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt index d450ed6..705e7f1 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt @@ -66,10 +66,14 @@ import java.util.concurrent.ExecutionException private const val IMAGE_JPG = "image/jpg" +private const val WHITE_COLOR_HEX = 0xFFFFFF + +private const val DEFAULT_FONT_SIZE = 16 + class ArticleFragment : Fragment(), DIAware { - private var fontSize: Int = 16 + private var fontSize: Int = DEFAULT_FONT_SIZE private lateinit var item: SelfossModel.Item private lateinit var url: String private lateinit var contentText: String @@ -100,6 +104,7 @@ class ArticleFragment : item = pi.toModel() } + @Suppress("detekt:LongMethod") override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -280,6 +285,7 @@ class ArticleFragment : } } + @Suppress("detekt:SwallowedException") private fun getContentFromMercury() { binding.progressBar.visibility = View.VISIBLE @@ -318,14 +324,14 @@ class ArticleFragment : } } - private fun handleLeadImage(lead_image_url: String?) { - if (!lead_image_url.isNullOrEmpty() && context != null) { + private fun handleLeadImage(leadImageUrl: String?) { + if (!leadImageUrl.isNullOrEmpty() && context != null) { binding.imageView.visibility = View.VISIBLE Glide .with(requireContext()) .asBitmap() .load( - lead_image_url, + leadImageUrl, ).apply(RequestOptions.fitCenterTransform()) .into(binding.imageView) } else { @@ -351,12 +357,14 @@ class ArticleFragment : false } + @Suppress("detekt:LongMethod", "detekt:SwallowedException") @Deprecated("Deprecated in Java") override fun shouldInterceptRequest( view: WebView, url: String, ): WebResourceResponse? { val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL) + var glideResource: WebResourceResponse? = null if (url.lowercase(Locale.US).contains(".jpg") || url .lowercase(Locale.US) @@ -371,11 +379,12 @@ class ArticleFragment : .load(url) .submit() .get() - return WebResourceResponse( - IMAGE_JPG, - "UTF-8", - getBitmapInputStream(image, Bitmap.CompressFormat.JPEG), - ) + glideResource = + WebResourceResponse( + IMAGE_JPG, + "UTF-8", + getBitmapInputStream(image, Bitmap.CompressFormat.JPEG), + ) } catch (e: ExecutionException) { // Do nothing } @@ -389,11 +398,12 @@ class ArticleFragment : .load(url) .submit() .get() - return WebResourceResponse( - IMAGE_JPG, - "UTF-8", - getBitmapInputStream(image, Bitmap.CompressFormat.PNG), - ) + glideResource = + WebResourceResponse( + IMAGE_JPG, + "UTF-8", + getBitmapInputStream(image, Bitmap.CompressFormat.PNG), + ) } catch (e: ExecutionException) { // Do nothing } @@ -407,21 +417,23 @@ class ArticleFragment : .load(url) .submit() .get() - return WebResourceResponse( - IMAGE_JPG, - "UTF-8", - getBitmapInputStream(image, Bitmap.CompressFormat.WEBP), - ) + glideResource = + WebResourceResponse( + IMAGE_JPG, + "UTF-8", + getBitmapInputStream(image, Bitmap.CompressFormat.WEBP), + ) } catch (e: ExecutionException) { // Do nothing } } - return super.shouldInterceptRequest(view, url) + return glideResource ?: super.shouldInterceptRequest(view, url) } } } + @Suppress("detekt:LongMethod", "detekt:ImplicitDefaultLocale") private fun htmlToWebview() { val context: Context try { @@ -451,13 +463,13 @@ class ArticleFragment : val colorSurfaceString = String.format( "#%06X", - 0xFFFFFF and (if (colorSurface.data != DATA_NULL_UNDEFINED) colorSurface.data else 0xFFFFFF), + WHITE_COLOR_HEX and (if (colorSurface.data != DATA_NULL_UNDEFINED) colorSurface.data else WHITE_COLOR_HEX), ) val colorOnSurfaceString = String.format( "#%06X", - 0xFFFFFF and (if (colorOnSurface.data != DATA_NULL_UNDEFINED) colorOnSurface.data else 0), + WHITE_COLOR_HEX and (if (colorOnSurface.data != DATA_NULL_UNDEFINED) colorOnSurface.data else 0), ) try { @@ -539,7 +551,7 @@ class ArticleFragment : | color: ${ String.format( "#%06X", - 0xFFFFFF and context.resources.getColor(R.color.colorAccent), + WHITE_COLOR_HEX and context.resources.getColor(R.color.colorAccent), ) } !important; | } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/FilterSheetFragment.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/FilterSheetFragment.kt index 307d3ac..bab0d24 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/FilterSheetFragment.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/FilterSheetFragment.kt @@ -33,6 +33,8 @@ import org.kodein.di.DIAware import org.kodein.di.android.x.closestDI import org.kodein.di.instance +private const val DRAWABLE_SIZE = 30 + class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { @@ -156,8 +158,8 @@ class FilterSheetFragment : } gd.setColor(gdColor) gd.shape = GradientDrawable.RECTANGLE - gd.setSize(30, 30) - gd.cornerRadius = 30F + gd.setSize(DRAWABLE_SIZE, DRAWABLE_SIZE) + gd.cornerRadius = DRAWABLE_SIZE.toFloat() c.chipIcon = gd } catch (e: Exception) { e.sendSilentlyWithAcraWithName("tags > GradientDrawable") diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/model/AndroidIModelUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/model/AndroidIModelUtils.kt index 5c5f32e..426f537 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/model/AndroidIModelUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/model/AndroidIModelUtils.kt @@ -9,10 +9,12 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.request.RequestOptions +private const val PRELOAD_IMAGE_TIMEOUT = 10000 + fun SelfossModel.Item.preloadImages(context: Context): Boolean { val imageUrls = this.getImages() - val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000) + val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(PRELOAD_IMAGE_TIMEOUT) try { for (url in imageUrls) { diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/settings/SettingsActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/settings/SettingsActivity.kt index adce9bb..7d2fb3f 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/settings/SettingsActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/settings/SettingsActivity.kt @@ -26,6 +26,10 @@ import org.kodein.di.android.closestDI private const val TITLE_TAG = "settingsActivityTitle" +const val MAX_ITEMS_NUMBER = 200 + +private const val MIN_ITEMS_NUMBER = 1 + class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, @@ -143,7 +147,7 @@ class SettingsActivity : InputFilter { source, _, _, dest, _, _ -> try { val input: Int = (dest.toString() + source.toString()).toInt() - if (input in 1..200) return@InputFilter null + if (input in MIN_ITEMS_NUMBER..MAX_ITEMS_NUMBER) return@InputFilter null } catch (nfe: NumberFormatException) { Toast .makeText( diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/LinksUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/LinksUtils.kt index 5f546ee..1df697f 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/LinksUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/LinksUtils.kt @@ -72,6 +72,7 @@ fun Context.openUrlInBrowser(url: String) { this.mayBeStartActivity(intent) } +@Suppress("detekt:SwallowedException") fun Context.mayBeStartActivity(intent: Intent) { try { this.startActivity(intent) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/glide/GlideUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/glide/GlideUtils.kt index 2c083a8..bdb56bf 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/glide/GlideUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/glide/GlideUtils.kt @@ -32,12 +32,14 @@ fun Context.circularDrawable( .into(view.imageView) } +private const val BITMAP_INPUT_STREAM_COMPRESSION_QUALITY = 80 + fun getBitmapInputStream( bitmap: Bitmap, compressFormat: Bitmap.CompressFormat, ): InputStream { val byteArrayOutputStream = ByteArrayOutputStream() - bitmap.compress(compressFormat, 80, byteArrayOutputStream) + bitmap.compress(compressFormat, BITMAP_INPUT_STREAM_COMPRESSION_QUALITY, byteArrayOutputStream) val bitmapData: ByteArray = byteArrayOutputStream.toByteArray() return ByteArrayInputStream(bitmapData) } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt index ed5f788..b7bfe0a 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt @@ -3,7 +3,6 @@ package bou.amine.apps.readerforselfossv2.android.utils.network import android.content.Context import android.net.ConnectivityManager import android.net.NetworkCapabilities -import android.os.Build import com.google.android.material.snackbar.Snackbar lateinit var s: Snackbar @@ -11,19 +10,13 @@ lateinit var s: Snackbar fun isNetworkAccessible(context: Context): Boolean { val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val network = connectivityManager.activeNetwork ?: return false - val networkCapabilities = connectivityManager.getNetworkCapabilities(network) ?: return false + val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) ?: return false - return when { - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true - else -> false - } - } else { - val network = connectivityManager.activeNetworkInfo ?: return false - return network.isConnectedOrConnecting + return when { + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true + else -> false } } diff --git a/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/tests/repository/RepositoryTest.kt b/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/tests/repository/RepositoryTest.kt index c5287ed..3eebe02 100644 --- a/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/tests/repository/RepositoryTest.kt +++ b/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/tests/repository/RepositoryTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("detekt:LargeClass") + package bou.amine.apps.readerforselfossv2.tests.repository import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB @@ -42,9 +44,9 @@ private const val FEED_URL = "https://test.com/feed" private const val TAGS = "Test, New" -private val NUMBER_ARTICLES = 100 -private val NUMBER_UNREAD = 50 -private val NUMBER_STARRED = 20 +private const val NUMBER_ARTICLES = 100 +private const val NUMBER_UNREAD = 50 +private const val NUMBER_STARRED = 20 class RepositoryTest { private val db = mockk(relaxed = true) diff --git a/detekt.yml b/detekt.yml new file mode 100644 index 0000000..b947317 --- /dev/null +++ b/detekt.yml @@ -0,0 +1,786 @@ +build: + maxIssues: 0 + excludeCorrectable: false + weights: + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 + +config: + validation: true + warningsAsErrors: false + checkExhaustiveness: false + # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' + excludes: '' + +processors: + active: true + exclude: + - 'DetektProgressListener' + # - 'KtFileCountProcessor' + # - 'PackageCountProcessor' + # - 'ClassCountProcessor' + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ProjectComplexityProcessor' + # - 'ProjectCognitiveComplexityProcessor' + # - 'ProjectLLOCProcessor' + # - 'ProjectCLOCProcessor' + # - 'ProjectLOCProcessor' + # - 'ProjectSLOCProcessor' + # - 'LicenseHeaderLoaderExtension' + +console-reports: + active: true + exclude: + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'NotificationReport' + - 'FindingsReport' + - 'FileBasedFindingsReport' + # - 'LiteFindingsReport' + +output-reports: + active: true + exclude: + # - 'TxtOutputReport' + # - 'XmlOutputReport' + # - 'HtmlOutputReport' + # - 'MdOutputReport' + # - 'SarifOutputReport' + +comments: + active: true + AbsentOrWrongFileLicense: + active: false + licenseTemplateFile: 'license.template' + licenseTemplateIsRegex: false + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + DeprecatedBlockTag: + active: false + EndOfSentenceFormat: + active: false + endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' + KDocReferencesNonPublicProperty: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + OutdatedDocumentation: + active: false + matchTypeParameters: true + matchDeclarationsOrder: true + allowParamOnConstructorProperties: false + UndocumentedPublicClass: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + searchInProtectedClass: false + UndocumentedPublicFunction: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + searchProtectedFunction: false + UndocumentedPublicProperty: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + searchProtectedProperty: false + +complexity: + active: true + CognitiveComplexMethod: + active: false + threshold: 15 + ComplexCondition: + active: true + threshold: 4 + ComplexInterface: + active: false + threshold: 10 + includeStaticDeclarations: false + includePrivateDeclarations: false + ignoreOverloaded: false + CyclomaticComplexMethod: + active: true + threshold: 15 + ignoreSingleWhenExpression: false + ignoreSimpleWhenEntries: false + ignoreNestingFunctions: false + nestingFunctions: + - 'also' + - 'apply' + - 'forEach' + - 'isNotNull' + - 'ifNull' + - 'let' + - 'run' + - 'use' + - 'with' + LabeledExpression: + active: false + ignoredLabels: [ ] + LargeClass: + active: true + threshold: 600 + LongMethod: + active: true + threshold: 60 + LongParameterList: + active: true + functionThreshold: 6 + constructorThreshold: 7 + ignoreDefaultParameters: false + ignoreDataClasses: true + ignoreAnnotatedParameter: [ ] + MethodOverloading: + active: false + threshold: 6 + NamedArguments: + active: false + threshold: 3 + ignoreArgumentsMatchingNames: false + NestedBlockDepth: + active: true + threshold: 4 + NestedScopeFunctions: + active: false + threshold: 1 + functions: + - 'kotlin.apply' + - 'kotlin.run' + - 'kotlin.with' + - 'kotlin.let' + - 'kotlin.also' + ReplaceSafeCallChainWithRun: + active: false + StringLiteralDuplication: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + threshold: 3 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/android/*Activity.kt', '**/fragments/*Fragment.kt' ] + thresholdInFiles: 11 + thresholdInClasses: 11 + thresholdInInterfaces: 11 + thresholdInObjects: 11 + thresholdInEnums: 11 + ignoreDeprecated: false + ignorePrivate: false + ignoreOverridden: false + ignoreAnnotatedFunctions: [ ] + +coroutines: + active: true + GlobalCoroutineUsage: + active: false + InjectDispatcher: + active: true + dispatcherNames: + - 'IO' + - 'Default' + - 'Unconfined' + RedundantSuspendModifier: + active: true + SleepInsteadOfDelay: + active: true + SuspendFunSwallowedCancellation: + active: false + SuspendFunWithCoroutineScopeReceiver: + active: false + SuspendFunWithFlowReturnType: + active: true + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: '_|(ignore|expected).*' + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + ignoreOverridden: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyTryBlock: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ExceptionRaisedInUnexpectedLocation: + active: true + methodNames: + - 'equals' + - 'finalize' + - 'hashCode' + - 'toString' + InstanceOfCheckForException: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + NotImplementedDeclaration: + active: false + ObjectExtendsThrowable: + active: false + PrintStackTrace: + active: true + RethrowCaughtException: + active: true + ReturnFromFinally: + active: true + ignoreLabeled: false + SwallowedException: + active: true + ignoredExceptionTypes: + - 'InterruptedException' + - 'MalformedURLException' + - 'NumberFormatException' + - 'ParseException' + allowedExceptionNameRegex: '_|(ignore|expected).*' + ThrowingExceptionFromFinally: + active: true + ThrowingExceptionInMain: + active: false + ThrowingExceptionsWithoutMessageOrCause: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + exceptions: + - 'ArrayIndexOutOfBoundsException' + - 'Exception' + - 'IllegalArgumentException' + - 'IllegalMonitorStateException' + - 'IllegalStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + ThrowingNewInstanceOfSameException: + active: true + TooGenericExceptionCaught: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + exceptionNames: + - 'ArrayIndexOutOfBoundsException' + - 'Error' + - 'Exception' + - 'IllegalMonitorStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + allowedExceptionNameRegex: '_|(ignore|expected).*' + TooGenericExceptionThrown: + active: true + exceptionNames: + - 'Error' + - 'Exception' + - 'RuntimeException' + - 'Throwable' + +naming: + active: true + BooleanPropertyNaming: + active: false + allowedPattern: '^(is|has|are)' + ClassNaming: + active: true + classPattern: '[A-Z][a-zA-Z0-9]*' + ConstructorParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + EnumNaming: + active: true + enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: false + forbiddenName: [ ] + FunctionMaxLength: + active: false + maximumFunctionNameLength: 30 + FunctionMinLength: + active: false + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + functionPattern: '[a-z][a-zA-Z0-9]*' + excludeClassPattern: '$^' + FunctionParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + InvalidPackageDeclaration: + active: true + rootPackage: '' + requireRootInDeclaration: false + LambdaParameterNaming: + active: false + parameterPattern: '[a-z][A-Za-z0-9]*|_' + MatchingDeclarationName: + active: false # done in ktlint + mustBeFirst: true + MemberNameEqualsClassName: + active: true + ignoreOverridden: true + NoNameShadowing: + active: true + NonBooleanPropertyPrefixedWithIs: + active: false + ObjectPropertyNaming: + active: true + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' + TopLevelPropertyNaming: + active: true + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: false + maximumVariableNameLength: 64 + VariableMinLength: + active: false + minimumVariableNameLength: 1 + VariableNaming: + active: true + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + +performance: + active: true + ArrayPrimitive: + active: true + CouldBeSequence: + active: false + threshold: 3 + ForEachOnRange: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + SpreadOperator: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + UnnecessaryPartOfBinaryExpression: + active: false + UnnecessaryTemporaryInstantiation: + active: true + +potential-bugs: + active: true + AvoidReferentialEquality: + active: true + forbiddenTypePatterns: + - 'kotlin.String' + CastNullableToNonNullableType: + active: false + CastToNullableType: + active: false + Deprecation: + active: false + DontDowncastCollectionTypes: + active: false + DoubleMutabilityForCollection: + active: true + mutableTypes: + - 'kotlin.collections.MutableList' + - 'kotlin.collections.MutableMap' + - 'kotlin.collections.MutableSet' + - 'java.util.ArrayList' + - 'java.util.LinkedHashSet' + - 'java.util.HashSet' + - 'java.util.LinkedHashMap' + - 'java.util.HashMap' + ElseCaseInsteadOfExhaustiveWhen: + active: false + ignoredSubjectTypes: [ ] + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExitOutsideMain: + active: false + ExplicitGarbageCollectionCall: + active: true + HasPlatformType: + active: true + IgnoredReturnValue: + active: true + restrictToConfig: true + returnValueAnnotations: + - 'CheckResult' + - '*.CheckResult' + - 'CheckReturnValue' + - '*.CheckReturnValue' + ignoreReturnValueAnnotations: + - 'CanIgnoreReturnValue' + - '*.CanIgnoreReturnValue' + returnValueTypes: + - 'kotlin.sequences.Sequence' + - 'kotlinx.coroutines.flow.*Flow' + - 'java.util.stream.*Stream' + ignoreFunctionCall: [ ] + ImplicitDefaultLocale: + active: true + ImplicitUnitReturnType: + active: false + allowExplicitReturnType: true + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + ignoreOnClassesPattern: '' + MapGetWithNotNullAssertionOperator: + active: true + MissingPackageDeclaration: + active: false + excludes: [ '**/*.kts' ] + NullCheckOnMutableProperty: + active: false + NullableToStringCall: + active: false + PropertyUsedBeforeDeclaration: + active: false + UnconditionalJumpStatementInLoop: + active: false + UnnecessaryNotNullCheck: + active: false + UnnecessaryNotNullOperator: + active: true + UnnecessarySafeCall: + active: true + UnreachableCatchBlock: + active: true + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + UnsafeCast: + active: true + UnusedUnaryOperator: + active: true + UselessPostfixExpression: + active: true + WrongEqualsTypeParameter: + active: true + +style: + active: true + AlsoCouldBeApply: + active: false + BracesOnIfStatements: + active: false + singleLine: 'never' + multiLine: 'always' + BracesOnWhenStatements: + active: false + singleLine: 'necessary' + multiLine: 'consistent' + CanBeNonNullable: + active: false + CascadingCallWrapping: + active: false + includeElvis: true + ClassOrdering: + active: false + CollapsibleIfStatements: + active: false + DataClassContainsFunctions: + active: false + conversionFunctionPrefix: + - 'to' + allowOperators: false + DataClassShouldBeImmutable: + active: false + DestructuringDeclarationWithTooManyEntries: + active: true + maxDestructuringEntries: 3 + DoubleNegativeLambda: + active: false + negativeFunctions: + - reason: 'Use `takeIf` instead.' + value: 'takeUnless' + - reason: 'Use `all` instead.' + value: 'none' + negativeFunctionNameParts: + - 'not' + - 'non' + EqualsNullCall: + active: true + EqualsOnSignatureLine: + active: false + ExplicitCollectionElementAccessMethod: + active: false + ExplicitItLambdaParameter: + active: true + ExpressionBodySyntax: + active: false + includeLineWrapping: false + ForbiddenAnnotation: + active: false + annotations: + - reason: 'it is a java annotation. Use `Suppress` instead.' + value: 'java.lang.SuppressWarnings' + - reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.' + value: 'java.lang.Deprecated' + - reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.' + value: 'java.lang.annotation.Documented' + - reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.' + value: 'java.lang.annotation.Target' + - reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.' + value: 'java.lang.annotation.Retention' + - reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.' + value: 'java.lang.annotation.Repeatable' + - reason: 'Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265' + value: 'java.lang.annotation.Inherited' + ForbiddenComment: + active: true + comments: + - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.' + value: 'FIXME:' + - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.' + value: 'STOPSHIP:' + - reason: 'Forbidden TODO todo marker in comment, please do the changes.' + value: 'TODO:' + allowedPatterns: '' + ForbiddenImport: + active: false + imports: [ ] + forbiddenPatterns: '' + ForbiddenMethodCall: + active: false + methods: + - reason: 'print does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.print' + - reason: 'println does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.println' + ForbiddenSuppress: + active: false + rules: [ ] + ForbiddenVoid: + active: true + ignoreOverridden: false + ignoreUsageInGenerics: false + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + ignoreActualFunction: true + excludedFunctions: [ ] + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 1 + MagicNumber: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts' ] + ignoreNumbers: + - '-1' + - '0' + - '1' + - '2' + ignoreHashCodeFunction: true + ignorePropertyDeclaration: false + ignoreLocalVariableDeclaration: false + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: false + ignoreRanges: false + ignoreExtensionFunctions: true + MandatoryBracesLoops: + active: false + MaxChainedCallsOnSameLine: + active: false + maxChainedCalls: 5 + MaxLineLength: + active: false # done in ktlint + maxLineLength: 140 # default is 120. 140 to match ktlint + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + excludeRawStrings: true + MayBeConst: + active: true + ModifierOrder: + active: true + MultilineLambdaItParameter: + active: false + MultilineRawStringIndentation: + active: false + indentSize: 4 + trimmingMethods: + - 'trimIndent' + - 'trimMargin' + NestedClassesVisibility: + active: true + NewLineAtEndOfFile: + active: false # done in ktlint + NoTabs: + active: false + NullableBooleanCheck: + active: false + ObjectLiteralToLambda: + active: true + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: false + PreferToOverPairSyntax: + active: false + ProtectedMemberInFinalClass: + active: true + RedundantExplicitType: + active: false + RedundantHigherOrderMapUsage: + active: true + RedundantVisibilityModifierRule: + active: false + ReturnCount: + active: true + max: 2 + excludedFunctions: + - 'equals' + excludeLabeled: false + excludeReturnFromLambda: true + excludeGuardClauses: false + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: true + SpacingBetweenPackageAndImports: + active: false + StringShouldBeRawString: + active: false + maxEscapedCharacterCount: 2 + ignoredCharacters: [ ] + ThrowsCount: + active: true + max: 2 + excludeGuardClauses: false + TrailingWhitespace: + active: false + TrimMultilineRawString: + active: false + trimmingMethods: + - 'trimIndent' + - 'trimMargin' + UnderscoresInNumericLiterals: + active: false + acceptableLength: 4 + allowNonStandardGrouping: false + UnnecessaryAbstractClass: + active: true + UnnecessaryAnnotationUseSiteTarget: + active: false + UnnecessaryApply: + active: true + UnnecessaryBackticks: + active: false + UnnecessaryBracesAroundTrailingLambda: + active: false + UnnecessaryFilter: + active: true + UnnecessaryInheritance: + active: true + UnnecessaryInnerClass: + active: false + UnnecessaryLet: + active: false + UnnecessaryParentheses: + active: false + allowForUnclearPrecedence: false + UntilInsteadOfRangeTo: + active: false + UnusedImports: + active: false + UnusedParameter: + active: true + allowedNames: 'ignored|expected' + UnusedPrivateClass: + active: true + UnusedPrivateMember: + active: true + allowedNames: '' + UnusedPrivateProperty: + active: true + allowedNames: '_|ignored|expected|serialVersionUID' + excludes: [ '**/build.gradle.kts' ] + UseAnyOrNoneInsteadOfFind: + active: true + UseArrayLiteralsInAnnotations: + active: true + UseCheckNotNull: + active: true + UseCheckOrError: + active: true + UseDataClass: + active: false + allowVars: false + UseEmptyCounterpart: + active: false + UseIfEmptyOrIfBlank: + active: false + UseIfInsteadOfWhen: + active: false + ignoreWhenContainingVariableDeclaration: false + UseIsNullOrEmpty: + active: true + UseLet: + active: false + UseOrEmpty: + active: true + UseRequire: + active: true + UseRequireNotNull: + active: true + UseSumOfInsteadOfFlatMapSize: + active: false + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: true + ignoreLateinitVar: false + WildcardImport: + active: true + excludeImports: + - 'java.util.*' diff --git a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/NetworkSSL.kt b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/NetworkSSL.kt index a335c73..901ab2c 100644 --- a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/NetworkSSL.kt +++ b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/NetworkSSL.kt @@ -9,12 +9,14 @@ class NaiveTrustManager : X509TrustManager { chain: Array?, authType: String?, ) { + // Nothing } override fun checkServerTrusted( chain: Array?, authType: String?, ) { + // Nothing } override fun getAcceptedIssuers(): Array = arrayOf() diff --git a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt index eb8a38d..be6f8aa 100644 --- a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt +++ b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt @@ -3,6 +3,7 @@ package bou.amine.apps.readerforselfossv2.utils import android.text.format.DateUtils import kotlinx.datetime.Clock +@Suppress("detekt:UtilityClassWithPublicConstructor") actual class DateUtils { actual companion object { actual fun parseRelativeDate(dateString: String): String { diff --git a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ModelConverters.kt b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ModelConverters.kt index 6553890..9ad4fec 100644 --- a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ModelConverters.kt +++ b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ModelConverters.kt @@ -12,16 +12,14 @@ actual fun SelfossModel.Item.getIcon(baseUrl: String): String = constructUrl(bas actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String = constructUrl(baseUrl, "thumbnails", thumbnail) +val IMAGE_EXTENSION_REGEXP = """\.(jpg|jpeg|png|webp)""".toRegex() + actual fun SelfossModel.Item.getImages(): ArrayList { val allImages = ArrayList() for (image in Jsoup.parse(content).getElementsByTag("img")) { val url = image.attr("src") - if (url.lowercase(Locale.US).contains(".jpg") || - url.lowercase(Locale.US).contains(".jpeg") || - url.lowercase(Locale.US).contains(".png") || - url.lowercase(Locale.US).contains(".webp") - ) { + if (IMAGE_EXTENSION_REGEXP.containsMatchIn(url.lowercase(Locale.US))) { allImages.add(url) } } diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/MercuryModel.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/MercuryModel.kt index 05c7666..bd854b0 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/MercuryModel.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/MercuryModel.kt @@ -1,13 +1,16 @@ +@file:Suppress("detekt:LongParameterList") + package bou.amine.apps.readerforselfossv2.model import kotlinx.serialization.Serializable class MercuryModel { + @Suppress("detekt:ConstructorParameterNaming") @Serializable class ParsedContent( val title: String? = null, val content: String? = null, - val lead_image_url: String? = null, // NOSONAR + val lead_image_url: String? = null, val url: String? = null, val error: Boolean? = null, val message: String? = null, diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/SelfossModel.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/SelfossModel.kt index 1cc4d18..8f1f74c 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/SelfossModel.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/SelfossModel.kt @@ -1,3 +1,5 @@ +@file:Suppress("detekt:LongParameterList") + package bou.amine.apps.readerforselfossv2.model import bou.amine.apps.readerforselfossv2.utils.DateUtils @@ -18,6 +20,10 @@ import kotlinx.serialization.json.booleanOrNull import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonPrimitive +class ModelException( + message: String, +) : Throwable(message) + class SelfossModel { @Serializable data class Tag( @@ -141,7 +147,7 @@ class SelfossModel { } if (stringUrl.isEmptyOrNullOrNullString()) { - throw Exception("Link $link was translated to $stringUrl, but was empty. Handle this.") + throw ModelException("Link $link was translated to $stringUrl, but was empty. Handle this.") } return stringUrl @@ -170,7 +176,7 @@ class SelfossModel { } } - // TODO: this seems to be super slow. + // this seems to be super slow. object TagsListSerializer : KSerializer> { override fun deserialize(decoder: Decoder): List = when (val json = ((decoder as JsonDecoder).decodeJsonElement())) { diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/Repository.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/Repository.kt index 38fb97d..b842903 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/Repository.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/Repository.kt @@ -1,3 +1,5 @@ +@file:Suppress("detekt:TooManyFunctions") + package bou.amine.apps.readerforselfossv2.repository import bou.amine.apps.readerforselfossv2.dao.ACTION @@ -23,6 +25,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +private const val MAX_ITEMS_NUMBER = 200 + class Repository( private val api: SelfossApi, private val appSettingsService: AppSettingsService, @@ -127,7 +131,7 @@ class Repository( null, null, null, - 200, + MAX_ITEMS_NUMBER, ) return if (items.success && items.data != null) { items.data @@ -139,6 +143,7 @@ class Repository( } } + @Suppress("detekt:ForbiddenComment") suspend fun reloadBadges(): Boolean { var success = false if (isNetworkAvailable()) { @@ -559,6 +564,7 @@ class Repository( item.id.toString(), ) + @Suppress("detekt:SwallowedException") suspend fun tryToCacheItemsAndGetNewOnes(): List { try { val newItems = getMaxItemsForBackground(ItemType.UNREAD) diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/RestUtils.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/RestUtils.kt index 82098b8..d072a63 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/RestUtils.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/RestUtils.kt @@ -33,6 +33,7 @@ suspend fun maybeResponse(r: HttpResponse?): SuccessResponse = SuccessResponse(false) } +@Suppress("detekt:SwallowedException") suspend inline fun bodyOrFailure(r: HttpResponse?): StatusAndData { try { return if (r != null && r.status.isSuccess()) { diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt index f4562f3..9bab495 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt @@ -1,3 +1,5 @@ +@file:Suppress("detekt:TooManyFunctions", "detekt:LongParameterList", "detekt:LargeClass") + package bou.amine.apps.readerforselfossv2.rest import bou.amine.apps.readerforselfossv2.model.SelfossModel @@ -35,6 +37,8 @@ import kotlinx.serialization.json.Json expect fun setupInsecureHttpEngine(config: CIOEngineConfig) +private const val VERSION_WHERE_POST_LOGIN_SHOULD_WORK = 5 + class SelfossApi( private val appSettingsService: AppSettingsService, ) { @@ -176,7 +180,7 @@ class SelfossApi( }, ) - private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0 + private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= VERSION_WHERE_POST_LOGIN_SHOULD_WORK // We are missing 4.1.0 suspend fun logout(): SuccessResponse = if (shouldHaveNewLogout()) { diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ACRASettings.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ACRASettings.kt index cfba825..e3c9644 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ACRASettings.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ACRASettings.kt @@ -1,3 +1,5 @@ +@file:Suppress("detekt:TooManyFunctions") + package bou.amine.apps.readerforselfossv2.service import com.russhwolf.settings.Settings diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/AppSettingsService.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/AppSettingsService.kt index 6713ad1..505ec74 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/AppSettingsService.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/AppSettingsService.kt @@ -1,7 +1,19 @@ +@file:Suppress("detekt:TooManyFunctions") + package bou.amine.apps.readerforselfossv2.service import com.russhwolf.settings.Settings +private const val DEFAULT_FONT_SIZE = 16 + +private const val DEFAULT_REFRESH_MINUTES = 360L + +private const val MIN_REFRESH_MINUTES = 15L + +private const val DEFAULT_API_TIMEOUT = 60L + +private const val DEFAULT_ITEMS_NUMBER = 20 + class AppSettingsService( acraSenderServiceProcess: Boolean = false, ) { @@ -36,7 +48,7 @@ class AppSettingsService( private var notifyNewItems: Boolean? = null private var itemsNumber: Int? = null private var apiTimeout: Long? = null - private var refreshMinutes: Long = 360 + private var refreshMinutes: Long = DEFAULT_REFRESH_MINUTES private var markOnScroll: Boolean? = null private var activeAlignment: Int? = null @@ -141,13 +153,14 @@ class AppSettingsService( return itemsNumber!! } + @Suppress("detekt:SwallowedException") private fun refreshItemsNumber() { itemsNumber = try { - settings.getString(API_ITEMS_NUMBER, "20").toInt() + settings.getString(API_ITEMS_NUMBER, DEFAULT_ITEMS_NUMBER.toString()).toInt() } catch (e: Exception) { settings.remove(API_ITEMS_NUMBER) - 20 + DEFAULT_ITEMS_NUMBER } } @@ -158,22 +171,24 @@ class AppSettingsService( return apiTimeout!! } + @Suppress("detekt:MagicNumber") private fun secToMs(n: Long) = n * 1000 + @Suppress("detekt:SwallowedException") private fun refreshApiTimeout() { apiTimeout = secToMs( try { - val settingsTimeout = settings.getString(API_TIMEOUT, "60") + val settingsTimeout = settings.getString(API_TIMEOUT, DEFAULT_API_TIMEOUT.toString()) if (settingsTimeout.toLong() > 0) { settingsTimeout.toLong() } else { settings.remove(API_TIMEOUT) - 60 + DEFAULT_API_TIMEOUT } } catch (e: Exception) { settings.remove(API_TIMEOUT) - 60 + DEFAULT_API_TIMEOUT }, ) } @@ -287,14 +302,14 @@ class AppSettingsService( } private fun refreshRefreshMinutes() { - refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, "360").toLong() - if (refreshMinutes <= 15) { - refreshMinutes = 15 + refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, DEFAULT_REFRESH_MINUTES.toString()).toLong() + if (refreshMinutes <= MIN_REFRESH_MINUTES) { + refreshMinutes = MIN_REFRESH_MINUTES } } fun getRefreshMinutes(): Long { - if (refreshMinutes != 360L) { + if (refreshMinutes != DEFAULT_REFRESH_MINUTES) { refreshRefreshMinutes() } return refreshMinutes @@ -368,7 +383,7 @@ class AppSettingsService( if (fontSize != null) { refreshFontSize() } - return fontSize ?: 16 + return fontSize ?: DEFAULT_FONT_SIZE } private fun refreshStaticBarEnabled() { diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt index 4b68662..3678b0d 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt @@ -4,6 +4,12 @@ import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant +class DateParseException( + message: String, + e: Throwable? = null, +) : Throwable(message, e) + +@Suppress("detekt:ThrowsCount") fun String.toParsedDate(): Long { // Possible formats are // yyyy-mm-dd hh:mm:ss format @@ -21,17 +27,18 @@ fun String.toParsedDate(): Long { .find(this) ?.groups ?.get(1) - ?.value ?: throw Exception("Couldn't parse $this") + ?.value ?: throw DateParseException("Couldn't parse $this") } else { - throw Exception("Unrecognized format for $this") + throw DateParseException("Unrecognized format for $this") } } catch (e: Exception) { - throw Exception("parseDate failed for $this", e) + throw DateParseException("parseDate failed for $this", e) } return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds() } +@Suppress("detekt:UtilityClassWithPublicConstructor") expect class DateUtils() { companion object { fun parseRelativeDate(dateString: String): String diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/EntityUtils.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/EntityUtils.kt index 9e07b29..816e886 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/EntityUtils.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/EntityUtils.kt @@ -17,7 +17,7 @@ fun SOURCE.toView(): SelfossModel.SourceDetail = this.id.toInt(), this.title, null, - this.tags?.split(","), + this.tags.split(","), this.spout, this.error, this.icon, @@ -74,6 +74,7 @@ fun SelfossModel.Item.toEntity(): ITEM = this.author, ) +@Suppress("detekt:MagicNumber") fun SelfossModel.Tag.getColorHexCode(): String = if (this.color.length == 4) { // #000 val char1 = this.color.get(1) diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ItemType.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ItemType.kt index ef53ba2..a61b2e0 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ItemType.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ItemType.kt @@ -1,5 +1,6 @@ package bou.amine.apps.readerforselfossv2.utils +@Suppress("detekt:MagicNumber") enum class ItemType( val position: Int, val type: String, diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/StringUtils.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/StringUtils.kt index 7c9f0ca..5994183 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/StringUtils.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/StringUtils.kt @@ -2,6 +2,7 @@ package bou.amine.apps.readerforselfossv2.utils fun String?.isEmptyOrNullOrNullString(): Boolean = this == null || this == "null" || this.isEmpty() +@Suppress("detekt:MagicNumber") fun String.longHash(): Long { var h = 98764321261L val l = this.length diff --git a/shared/src/commonTest/kotlin/DatesTest.kt b/shared/src/commonTest/kotlin/bou/amine/apps/readerforselfossv2/repository/DatesTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/DatesTest.kt rename to shared/src/commonTest/kotlin/bou/amine/apps/readerforselfossv2/repository/DatesTest.kt diff --git a/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt b/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt index e90f3e1..1dae9ff 100644 --- a/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt +++ b/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt @@ -2,5 +2,6 @@ package bou.amine.apps.readerforselfossv2.rest import io.ktor.client.engine.cio.CIOEngineConfig +@Suppress("detekt:EmptyFunctionBlock") actual fun setupInsecureHttpEngine(config: CIOEngineConfig) { } diff --git a/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt b/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt index 1b5e402..d0e7996 100644 --- a/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt +++ b/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt @@ -1,5 +1,6 @@ package bou.amine.apps.readerforselfossv2.utils +@Suppress("detekt:UtilityClassWithPublicConstructor") actual class DateUtils { actual companion object { actual fun parseRelativeDate(dateString: String): String { diff --git a/shared/src/iosSimulatorArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.iosSimulatorArm64.kt b/shared/src/iosSimulatorArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt similarity index 82% rename from shared/src/iosSimulatorArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.iosSimulatorArm64.kt rename to shared/src/iosSimulatorArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt index 903125f..3eb0698 100644 --- a/shared/src/iosSimulatorArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.iosSimulatorArm64.kt +++ b/shared/src/iosSimulatorArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt @@ -1,5 +1,6 @@ package bou.amine.apps.readerforselfossv2.utils +@Suppress("detekt:UtilityClassWithPublicConstructor") actual class DateUtils actual constructor() { actual companion object { actual fun parseRelativeDate(dateString: String): String { diff --git a/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt b/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt index e90f3e1..1dae9ff 100644 --- a/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt +++ b/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt @@ -2,5 +2,6 @@ package bou.amine.apps.readerforselfossv2.rest import io.ktor.client.engine.cio.CIOEngineConfig +@Suppress("detekt:EmptyFunctionBlock") actual fun setupInsecureHttpEngine(config: CIOEngineConfig) { } diff --git a/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt b/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt index 1b5e402..d0e7996 100644 --- a/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt +++ b/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt @@ -1,5 +1,6 @@ package bou.amine.apps.readerforselfossv2.utils +@Suppress("detekt:UtilityClassWithPublicConstructor") actual class DateUtils { actual companion object { actual fun parseRelativeDate(dateString: String): String {