diff --git a/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/AddSourceEspressoTest.kt b/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/AddSourceEspressoTest.kt deleted file mode 100644 index 11a8688..0000000 --- a/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/AddSourceEspressoTest.kt +++ /dev/null @@ -1,3 +0,0 @@ -package bou.amine.apps.readerforselfossv2.android - -// TODO: test source adding \ No newline at end of file diff --git a/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/HomeActivityEspressoTest.kt b/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/HomeActivityEspressoTest.kt deleted file mode 100644 index 79cd207..0000000 --- a/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/HomeActivityEspressoTest.kt +++ /dev/null @@ -1,102 +0,0 @@ -package bou.amine.apps.readerforselfossv2.android - -import android.content.Context -import android.content.Intent -import androidx.test.InstrumentationRegistry -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu -import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.action.ViewActions.closeSoftKeyboard -import androidx.test.espresso.action.ViewActions.pressKey -import androidx.test.espresso.action.ViewActions.typeText -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.intent.Intents -import androidx.test.espresso.intent.Intents.intended -import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent -import androidx.test.espresso.matcher.ViewMatchers.isDisplayed -import androidx.test.espresso.matcher.ViewMatchers.withContentDescription -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.matcher.ViewMatchers.withText -import androidx.test.rule.ActivityTestRule -import androidx.test.runner.AndroidJUnit4 -import android.view.KeyEvent -import androidx.test.espresso.matcher.RootMatchers.isDialog -import bou.amine.apps.readerforselfossv2.android.HomeActivity -import bou.amine.apps.readerforselfossv2.android.LoginActivity -import bou.amine.apps.readerforselfossv2.android.utils.Config -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class HomeActivityEspressoTest { - lateinit var context: Context - - @Rule @JvmField - val rule = ActivityTestRule(HomeActivity::class.java, true, false) - - @Before - fun clearData() { - context = InstrumentationRegistry.getInstrumentation().targetContext - - val editor = - context - .getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) - .edit() - editor.clear() - - editor.putString("url", BuildConfig.LOGIN_URL) - editor.putString("login", BuildConfig.LOGIN_USERNAME) - editor.putString("password", BuildConfig.LOGIN_PASSWORD) - - editor.commit() - - Intents.init() - } - - @Test - fun menuItems() { - - rule.launchActivity(Intent()) - - onView( - withMenu( - id = R.id.action_search, - titleId = R.string.menu_home_search - ) - ).perform(click()) - - onView(withId(R.id.search_bar)).check(matches(isDisplayed())) - - onView(withId(R.id.search_src_text)).perform( - typeText("android"), - pressKey(KeyEvent.KEYCODE_SEARCH), - closeSoftKeyboard() - ) - - onView(withContentDescription(R.string.abc_toolbar_collapse_description)).perform(click()) - - openActionBarOverflowOrOptionsMenu(context) - - onView(withMenu(id = R.id.refresh, titleId = R.string.menu_home_refresh)) - .perform(click()) - - onView(withText(android.R.string.ok)) - .inRoot(isDialog()).check(matches(isDisplayed())).perform(click()) - - openActionBarOverflowOrOptionsMenu(context) - - onView(withText(R.string.action_disconnect)).perform(click()) - - intended(hasComponent(LoginActivity::class.java.name)) - } - - // TODO: test articles opening and actions for cards and lists - - @After - fun releaseIntents() { - Intents.release() - } -} \ No newline at end of file diff --git a/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/LoginActivityEspressoTest.kt b/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/LoginActivityEspressoTest.kt deleted file mode 100644 index e64adb3..0000000 --- a/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/LoginActivityEspressoTest.kt +++ /dev/null @@ -1,180 +0,0 @@ -package bou.amine.apps.readerforselfossv2.android - -import android.content.Context -import android.content.Intent -import androidx.test.InstrumentationRegistry -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu -import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.action.ViewActions.closeSoftKeyboard -import androidx.test.espresso.action.ViewActions.pressBack -import androidx.test.espresso.action.ViewActions.typeText -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.intent.Intents -import androidx.test.espresso.intent.Intents.intended -import androidx.test.espresso.intent.Intents.times -import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.espresso.matcher.ViewMatchers.isRoot -import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.matcher.ViewMatchers.withText -import androidx.test.rule.ActivityTestRule -import androidx.test.runner.AndroidJUnit4 -import bou.amine.apps.readerforselfossv2.android.HomeActivity -import bou.amine.apps.readerforselfossv2.android.LoginActivity -import bou.amine.apps.readerforselfossv2.android.utils.Config -import com.mikepenz.aboutlibraries.ui.LibsActivity -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class LoginActivityEspressoTest { - - @Rule @JvmField - val rule = ActivityTestRule(LoginActivity::class.java, true, false) - - private lateinit var context: Context - private lateinit var url: String - private lateinit var username: String - private lateinit var password: String - - @Before - fun setUp() { - context = InstrumentationRegistry.getInstrumentation().targetContext - val editor = - context - .getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) - .edit() - editor.clear() - editor.commit() - - - url = BuildConfig.LOGIN_URL - username = BuildConfig.LOGIN_USERNAME - password = BuildConfig.LOGIN_PASSWORD - - Intents.init() - } - - @Test - fun menuItems() { - - rule.launchActivity(Intent()) - - openActionBarOverflowOrOptionsMenu(context) - - onView(withText(R.string.action_about)).perform(click()) - - intended(hasComponent(LibsActivity::class.java.name), times(1)) - - onView(isRoot()).perform(pressBack()) - - intended(hasComponent(LoginActivity::class.java.name)) - } - - @Test - fun wrongLoginUrl() { - rule.launchActivity(Intent()) - - onView(withId(R.id.loginProgress)) - .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))) - - onView(withId(R.id.urlView)).perform(click()).perform(typeText("WRONGURL")) - - onView(withId(R.id.signInButton)).perform(click()) - - onView(withId(R.id.urlView)).check(matches(isHintOrErrorEnabled())) - } - - // TODO: Add tests for multiple false urls with dialog - - @Test - fun emptyAuthData() { - - rule.launchActivity(Intent()) - - onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard()) - - onView(withId(R.id.withLogin)).perform(click()) - - onView(withId(R.id.signInButton)).perform(click()) - - onView(withId(R.id.loginView)).check(matches(isHintOrErrorEnabled())) - onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled())) - - onView(withId(R.id.loginView)).perform(click()).perform( - typeText(username), - closeSoftKeyboard() - ) - - onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled())) - - onView(withId(R.id.signInButton)).perform(click()) - - onView(withId(R.id.passwordView)).check( - matches( - isHintOrErrorEnabled() - ) - ) - } - - @Test - fun wrongAuthData() { - - rule.launchActivity(Intent()) - - onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard()) - - onView(withId(R.id.withLogin)).perform(click()) - - onView(withId(R.id.loginView)).perform(click()).perform( - typeText(username), - closeSoftKeyboard() - ) - - onView(withId(R.id.passwordView)).perform(click()).perform( - typeText("WRONGPASS"), - closeSoftKeyboard() - ) - - onView(withId(R.id.signInButton)).perform(click()) - - onView(withId(R.id.urlView)).check(matches(isHintOrErrorEnabled())) - onView(withId(R.id.loginView)).check(matches(isHintOrErrorEnabled())) - onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled())) - } - - @Test - fun workingAuth() { - - rule.launchActivity(Intent()) - - onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard()) - - onView(withId(R.id.withLogin)).perform(click()) - - onView(withId(R.id.loginView)).perform(click()).perform( - typeText(username), - closeSoftKeyboard() - ) - - onView(withId(R.id.passwordView)).perform(click()).perform( - typeText(password), - closeSoftKeyboard() - ) - - onView(withId(R.id.signInButton)).perform(click()) - - Thread.sleep(2000) - intended(hasComponent(HomeActivity::class.java.name)) - } - - @After - fun releaseIntents() { - Intents.release() - } -} \ No newline at end of file diff --git a/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/MainActivityEspressoTest.kt b/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/MainActivityEspressoTest.kt deleted file mode 100644 index 1400a0e..0000000 --- a/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/MainActivityEspressoTest.kt +++ /dev/null @@ -1,81 +0,0 @@ -package bou.amine.apps.readerforselfossv2.android - -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import androidx.test.InstrumentationRegistry.getInstrumentation -import androidx.test.espresso.intent.Intents -import androidx.test.espresso.intent.Intents.intended -import androidx.test.espresso.intent.Intents.times -import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent -import androidx.test.rule.ActivityTestRule -import androidx.test.runner.AndroidJUnit4 -import bou.amine.apps.readerforselfossv2.android.HomeActivity -import bou.amine.apps.readerforselfossv2.android.LoginActivity -import bou.amine.apps.readerforselfossv2.android.MainActivity -import bou.amine.apps.readerforselfossv2.android.utils.Config -import org.junit.After - -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class MainActivityEspressoTest { - - lateinit var intent: Intent - lateinit var preferencesEditor: SharedPreferences.Editor - private lateinit var url: String - private lateinit var username: String - private lateinit var password: String - - @Rule @JvmField - val rule = ActivityTestRule(MainActivity::class.java, true, false) - - @Before - fun setUp() { - intent = Intent() - val context = getInstrumentation().targetContext - - // create a SharedPreferences editor - preferencesEditor = context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE).edit() - - url = BuildConfig.LOGIN_URL - username = BuildConfig.LOGIN_USERNAME - password = BuildConfig.LOGIN_PASSWORD - - Intents.init() - } - - @Test - fun checkFirstOpenLaunchesIntro() { - preferencesEditor.putString("url", "") - preferencesEditor.putString("password", "") - preferencesEditor.putString("login", "") - preferencesEditor.commit() - - rule.launchActivity(intent) - - intended(hasComponent(LoginActivity::class.java.name)) - intended(hasComponent(HomeActivity::class.java.name), times(0)) - } - - @Test - fun checkNotFirstOpenLaunchesLogin() { - preferencesEditor.putString("url", url) - preferencesEditor.putString("password", password) - preferencesEditor.putString("login", username) - preferencesEditor.commit() - - rule.launchActivity(intent) - - intended(hasComponent(MainActivity::class.java.name)) - intended(hasComponent(HomeActivity::class.java.name)) - } - - @After - fun releaseIntents() { - Intents.release() - } -} \ No newline at end of file diff --git a/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/Utils.kt b/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/Utils.kt deleted file mode 100644 index b8fd12a..0000000 --- a/androidApp/src/androidTest/java/apps/amine/bou/readerforselfoss/Utils.kt +++ /dev/null @@ -1,29 +0,0 @@ -package bou.amine.apps.readerforselfossv2.android - -import androidx.test.espresso.matcher.ViewMatchers -import android.view.View -import android.widget.EditText -import org.hamcrest.Description -import org.hamcrest.Matcher -import org.hamcrest.Matchers -import org.hamcrest.TypeSafeMatcher - -fun isHintOrErrorEnabled(): Matcher = - object : TypeSafeMatcher() { - override fun describeTo(description: Description?) { - } - - override fun matchesSafely(item: View?): Boolean { - if (item !is EditText) { - return false - } - - return item.error.isNotEmpty() - } - } - -fun withMenu(id: Int, titleId: Int): Matcher = - Matchers.anyOf( - ViewMatchers.withId(id), - ViewMatchers.withText(titleId) - ) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt index 7df13e1..fc9acb5 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt @@ -9,10 +9,10 @@ import androidx.constraintlayout.widget.ConstraintLayout import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.themes.Toppings -import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException import bou.amine.apps.readerforselfossv2.repository.Repository +import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import com.ftinc.scoop.Scoop import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -31,6 +31,7 @@ class AddSourceActivity : AppCompatActivity(), DIAware { override val di by closestDI() private val repository : Repository by instance() + private val apiDetailsService : ApiDetailsService by instance() override fun onCreate(savedInstanceState: Bundle?) { appColors = AppColors(this@AddSourceActivity) @@ -81,9 +82,9 @@ class AddSourceActivity : AppCompatActivity(), DIAware { override fun onResume() { super.onResume() - val config = Config() - if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid(this@AddSourceActivity)) { + val baseUrl = apiDetailsService.getBaseUrl() + if (baseUrl.isEmpty() || !baseUrl.isBaseUrlValid(this@AddSourceActivity)) { mustLoginToAddSource() } else { handleSpoutsSpinner(binding.spoutsSpinner, binding.progress, binding.formContainer) 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 87b5b67..51e32ca 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 @@ -30,13 +30,12 @@ import bou.amine.apps.readerforselfossv2.android.databinding.ActivityHomeBinding import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.themes.Toppings -import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper -import bou.amine.apps.readerforselfossv2.dao.ACTION import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.model.SelfossModel +import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import bou.amine.apps.readerforselfossv2.utils.* import com.ashokvarma.bottomnavigation.BottomNavigationBar import com.ashokvarma.bottomnavigation.BottomNavigationItem @@ -84,10 +83,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar private var displayUnreadCount = false private var displayAllCount = false private var fullHeightCards: Boolean = false - private var itemsNumber: Int = 200 private var elementsShown: ItemType = ItemType.UNREAD - private var displayAccountHeader: Boolean = false - private var infiniteScroll: Boolean = false private var lastFetchDone: Boolean = false private var updateSources: Boolean = true private var markOnScroll: Boolean = false @@ -114,8 +110,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar private lateinit var tagsBadge: Map - private lateinit var config: Config - override val di by closestDI() private val repository : Repository by instance() @@ -128,7 +122,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar override fun onCreate(savedInstanceState: Bundle?) { appColors = AppColors(this@HomeActivity) - config = Config() super.onCreate(savedInstanceState) binding = ActivityHomeBinding.inflate(layoutInflater) @@ -308,7 +301,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar reloadLayoutManager() - if (!infiniteScroll) { + if (!settings.getBoolean("infinite_loading", false)) { binding.recyclerView.setHasFixedSize(true) } else { handleInfiniteScroll() @@ -331,15 +324,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar } private fun handleSettings() { + // TODO : clean this internalBrowser = settings.getBoolean("prefer_internal_browser", true) articleViewer = settings.getBoolean("prefer_article_viewer", true) shouldBeCardView = settings.getBoolean("card_view_active", false) displayUnreadCount = settings.getBoolean("display_unread_count", true) displayAllCount = settings.getBoolean("display_other_count", false) fullHeightCards = settings.getBoolean("full_height_cards", false) - itemsNumber = settings.getString("prefer_api_items_number", "200").toInt() - displayAccountHeader = settings.getBoolean("account_header_displaying", false) - infiniteScroll = settings.getBoolean("infinite_loading", false) + updateSources = settings.getBoolean("update_sources", true) markOnScroll = settings.getBoolean("mark_on_scroll", false) hiddenTags = if (settings.getString("hidden_tags", "").isNotEmpty()) { @@ -409,7 +401,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar binding.drawerContainer.addDrawerListener(drawerListener) - displayAccountHeader = + val displayAccountHeader = settings.getBoolean("account_header_displaying", false) binding.mainDrawer.addStickyFooterItem( @@ -418,7 +410,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar iconRes = R.drawable.ic_bug_report_black_24dp isIconTinted = true onDrawerItemClickListener = { _, _, _ -> - val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(Config.trackerUrl)) + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(ApiDetailsService.trackerUrl)) startActivity(browserIntent) false } @@ -451,6 +443,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar } // TODO: refactor this. + // TODO: use updateSources private fun handleDrawerItems() { tagsBadge = emptyMap() fun handleDrawerData(maybeDrawerData: DrawerData?, loadedFromCache: Boolean = false) { @@ -875,11 +868,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar this, items, customTabActivityHelper, - internalBrowser, - articleViewer, - fullHeightCards, + internalBrowser, // TODO remove and use from apidetailsservice + articleViewer, // TODO remove and use from apidetailsservice + fullHeightCards, // TODO remove and use from apidetailsservice appColors, - config ) { updateItems(it) } @@ -889,10 +881,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar this, items, customTabActivityHelper, - internalBrowser, - articleViewer, + internalBrowser, // TODO remove and use from apidetailsservice + articleViewer, // TODO remove and use from apidetailsservice appColors, - config ) { updateItems(it) } @@ -1047,7 +1038,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar return true } R.id.action_disconnect -> { - return Config.logoutAndRedirect(this, this@HomeActivity) + val settings = Settings() + settings.clear() + val intent = Intent(this, LoginActivity::class.java) + this.startActivity(intent) + this@HomeActivity.finish() + return true } else -> return super.onOptionsItemSelected(item) } 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 3345124..9ad4e7e 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 @@ -14,12 +14,11 @@ import androidx.lifecycle.ProcessLifecycleOwner import androidx.multidex.MultiDexApplication import androidx.preference.PreferenceManager import bou.amine.apps.readerforselfossv2.DI.networkModule -import bou.amine.apps.readerforselfossv2.android.utils.Config -import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel import bou.amine.apps.readerforselfossv2.dao.DriverFactory import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB import bou.amine.apps.readerforselfossv2.repository.Repository +import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import com.ftinc.scoop.Scoop @@ -49,13 +48,11 @@ class MyApp : MultiDexApplication(), DIAware { private val viewModel: AppViewModel by instance() private val connectivityStatus: ConnectivityStatus by instance() private val driverFactory: DriverFactory by instance() - private lateinit var config: Config private lateinit var settings : Settings override fun onCreate() { super.onCreate() Napier.base(DebugAntilog()) - config = Config() settings = Settings() initDrawerImageLoader() @@ -93,11 +90,11 @@ class MyApp : MultiDexApplication(), DIAware { val name = getString(R.string.notification_channel_sync) val importance = NotificationManager.IMPORTANCE_LOW - val mChannel = NotificationChannel(Config.syncChannelId, name, importance) + val mChannel = NotificationChannel(ApiDetailsService.syncChannelId, name, importance) val newItemsChannelname = getString(R.string.new_items_channel_sync) val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT - val newItemsChannelmChannel = NotificationChannel(Config.newItemsChannelId, newItemsChannelname, newItemsChannelimportance) + val newItemsChannelmChannel = NotificationChannel(ApiDetailsService.newItemsChannelId, newItemsChannelname, newItemsChannelimportance) notificationManager.createNotificationChannel(mChannel) notificationManager.createNotificationChannel(newItemsChannelmChannel) @@ -108,7 +105,7 @@ class MyApp : MultiDexApplication(), DIAware { DrawerImageLoader.init(object : AbstractDrawerImageLoader() { override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) { Glide.with(imageView.context) - .loadMaybeBasicAuth(config, uri.toString()) + .load(uri.toString()) .apply(RequestOptions.fitCenterTransform().placeholder(placeholder)) .into(imageView) } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt index a7b42d6..36ef496 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt @@ -38,7 +38,6 @@ class ItemCardAdapter( private val articleViewer: Boolean, private val fullHeightCards: Boolean, override val appColors: AppColors, - override val config: Config, override val updateItems: (ArrayList) -> Unit ) : ItemsAdapter() { private val c: Context = app.baseContext @@ -78,7 +77,7 @@ class ItemCardAdapter( binding.itemImage.setImageDrawable(null) } else { binding.itemImage.visibility = View.VISIBLE - c.bitmapCenterCrop(config, itm.getThumbnail(repository.baseUrl), binding.itemImage) + c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage) } if (itm.getIcon(repository.baseUrl).isEmpty()) { @@ -91,7 +90,7 @@ class ItemCardAdapter( .build(itm.title.getHtmlDecoded().toTextDrawableString(), color) binding.sourceImage.setImageDrawable(drawable) } else { - c.circularBitmapDrawable(config, itm.getIcon(repository.baseUrl), binding.sourceImage) + c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage) } } } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemListAdapter.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemListAdapter.kt index 27839a4..6a9f30a 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemListAdapter.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemListAdapter.kt @@ -30,7 +30,6 @@ class ItemListAdapter( private val internalBrowser: Boolean, private val articleViewer: Boolean, override val appColors: AppColors, - override val config: Config, override val updateItems: (ArrayList) -> Unit ) : ItemsAdapter() { private val generator: ColorGenerator = ColorGenerator.MATERIAL @@ -69,10 +68,10 @@ class ItemListAdapter( binding.itemImage.setImageDrawable(drawable) } else { - c.circularBitmapDrawable(config, itm.getIcon(repository.baseUrl), binding.itemImage) + c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage) } } else { - c.bitmapCenterCrop(config, itm.getThumbnail(repository.baseUrl), binding.itemImage) + c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage) } } } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemsAdapter.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemsAdapter.kt index 7dea71a..99b8002 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemsAdapter.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemsAdapter.kt @@ -6,7 +6,6 @@ import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.themes.AppColors -import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.utils.ItemType @@ -21,7 +20,6 @@ abstract class ItemsAdapter : RecyclerView.Adapte abstract val repository: Repository abstract val app: Activity abstract val appColors: AppColors - abstract val config: Config abstract val updateItems: (ArrayList) -> Unit fun updateAllItems(items: ArrayList) { diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/SourcesListAdapter.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/SourcesListAdapter.kt index 187af5a..7d10979 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/SourcesListAdapter.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/SourcesListAdapter.kt @@ -11,7 +11,6 @@ import androidx.recyclerview.widget.RecyclerView import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString -import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.model.SelfossModel @@ -33,7 +32,6 @@ class SourcesListAdapter( ) : RecyclerView.Adapter(), DIAware { private val c: Context = app.baseContext private val generator: ColorGenerator = ColorGenerator.MATERIAL - private lateinit var config: Config private lateinit var binding: SourceListItemBinding override val di: DI by closestDI(app) @@ -46,7 +44,6 @@ class SourcesListAdapter( override fun onBindViewHolder(holder: ViewHolder, position: Int) { val itm = items[position] - config = Config() if (itm.getIcon(repository.baseUrl).isEmpty()) { val color = generator.getColor(itm.title.getHtmlDecoded()) @@ -58,7 +55,7 @@ class SourcesListAdapter( .build(itm.title.getHtmlDecoded().toTextDrawableString(), color) binding.itemImage.setImageDrawable(drawable) } else { - c.circularBitmapDrawable(config, itm.getIcon(repository.baseUrl), binding.itemImage) + c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage) } binding.sourceTitle.text = itm.title.getHtmlDecoded() diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt index 907338d..c86de60 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt @@ -14,11 +14,10 @@ import bou.amine.apps.readerforselfossv2.android.MainActivity import bou.amine.apps.readerforselfossv2.android.MyApp import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.model.preloadImages -import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible -import bou.amine.apps.readerforselfossv2.dao.ACTION import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.repository.Repository +import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import com.russhwolf.settings.Settings import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -27,7 +26,6 @@ import org.kodein.di.DIAware import org.kodein.di.instance import java.util.* import kotlin.concurrent.schedule -import kotlin.concurrent.thread class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params), DIAware { @@ -44,12 +42,12 @@ override fun doWork(): Result { applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val notification = - NotificationCompat.Builder(applicationContext, Config.syncChannelId) + NotificationCompat.Builder(applicationContext, ApiDetailsService.syncChannelId) .setContentTitle(context.getString(R.string.loading_notification_title)) .setContentText(context.getString(R.string.loading_notification_text)) .setOngoing(true) .setPriority(PRIORITY_LOW) - .setChannelId(Config.syncChannelId) + .setChannelId(ApiDetailsService.syncChannelId) .setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp) notificationManager.notify(1, notification.build()) @@ -89,7 +87,7 @@ override fun doWork(): Result { val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, pflags) val newItemsNotification = - NotificationCompat.Builder(applicationContext, Config.newItemsChannelId) + NotificationCompat.Builder(applicationContext, ApiDetailsService.newItemsChannelId) .setContentTitle(context.getString(R.string.new_items_notification_title)) .setContentText( context.getString( @@ -98,7 +96,7 @@ override fun doWork(): Result { ) ) .setPriority(PRIORITY_DEFAULT) - .setChannelId(Config.newItemsChannelId) + .setChannelId(ApiDetailsService.newItemsChannelId) .setContentIntent(pendingIntent) .setAutoCancel(true) .setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp) 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 7c9f602..c893954 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 @@ -29,7 +29,6 @@ import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.utils.* import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream -import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded @@ -70,7 +69,6 @@ class ArticleFragment : Fragment(), DIAware { private lateinit var fab: FloatingActionButton private lateinit var appColors: AppColors private lateinit var textAlignment: String - private lateinit var config: Config private var _binding: FragmentArticleBinding? = null private val binding get() = _binding!! @@ -93,7 +91,6 @@ class ArticleFragment : Fragment(), DIAware { override fun onCreate(savedInstanceState: Bundle?) { appColors = AppColors(requireActivity()) - config = Config() super.onCreate(savedInstanceState) @@ -213,7 +210,7 @@ class ArticleFragment : Fragment(), DIAware { Glide .with(requireContext()) .asBitmap() - .loadMaybeBasicAuth(config, contentImage) + .load(contentImage) .apply(RequestOptions.fitCenterTransform()) .into(binding.imageView) } else { @@ -307,8 +304,7 @@ class ArticleFragment : Fragment(), DIAware { Glide .with(requireContext()) .asBitmap() - .loadMaybeBasicAuth( - config, + .load( response.body()!!.lead_image_url.orEmpty() ) .apply(RequestOptions.fitCenterTransform()) 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 f4382ce..050e6a8 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 @@ -15,7 +15,7 @@ import androidx.preference.PreferenceFragmentCompat import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySettingsBinding import bou.amine.apps.readerforselfossv2.android.themes.Toppings -import bou.amine.apps.readerforselfossv2.android.utils.Config +import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import com.ftinc.scoop.Scoop import com.russhwolf.settings.Settings import java.lang.NumberFormatException @@ -196,17 +196,17 @@ class SettingsActivity : AppCompatActivity(), setPreferencesFromResource(R.xml.pref_links, rootKey) preferenceManager.findPreference("trackerLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener { - openUrl(Uri.parse(Config.trackerUrl)) + openUrl(Uri.parse(ApiDetailsService.trackerUrl)) true } preferenceManager.findPreference("sourceLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener { - openUrl(Uri.parse(Config.sourceUrl)) + openUrl(Uri.parse(ApiDetailsService.sourceUrl)) false } preferenceManager.findPreference("translation")?.onPreferenceClickListener = Preference.OnPreferenceClickListener { - openUrl(Uri.parse(Config.translationUrl)) + openUrl(Uri.parse(ApiDetailsService.translationUrl)) false } } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/Config.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/Config.kt deleted file mode 100644 index 9ce7ede..0000000 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/Config.kt +++ /dev/null @@ -1,62 +0,0 @@ -package bou.amine.apps.readerforselfossv2.android.utils - -import android.app.Activity -import android.content.Context -import android.content.Intent -import bou.amine.apps.readerforselfossv2.android.LoginActivity -import com.russhwolf.settings.Settings - -class Config { - - val settings = Settings() - - val baseUrl: String - get() = settings.getString("url", "") - - val userLogin: String - get() = settings.getString("login", "") - - val userPassword: String - get() = settings.getString("password", "") - - val httpUserLogin: String - get() = settings.getString("httpUserName", "") - - val httpUserPassword: String - get() = settings.getString("httpPassword", "") - - companion object { - const val settingsName = "paramsselfoss" - - const val feedbackEmail = "aminecmi@pm.me.com" - - const val translationUrl = "https://crwd.in/readerforselfoss" - - const val sourceUrl = "https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform" - - const val trackerUrl = "https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues" - - const val syncChannelId = "sync-channel-id" - - const val newItemsChannelId = "new-items-channel-id" - - var apiVersion = 0 - - /* Execute logout and clear all settings to default */ - fun logoutAndRedirect( - c: Context, - callingActivity: Activity, - baseUrlFail: Boolean = false - ): Boolean { - val settings = Settings() - settings.clear() - val intent = Intent(c, LoginActivity::class.java) - if (baseUrlFail) { - intent.putExtra("baseUrlFail", baseUrlFail) - } - c.startActivity(intent) - callingActivity.finish() - return true - } - } -} 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 be63485..5d91e62 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 @@ -2,33 +2,26 @@ package bou.amine.apps.readerforselfossv2.android.utils.glide import android.content.Context import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import android.util.Base64 -import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import android.widget.ImageView -import bou.amine.apps.readerforselfossv2.android.utils.Config +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import com.bumptech.glide.Glide -import com.bumptech.glide.RequestBuilder -import com.bumptech.glide.RequestManager -import com.bumptech.glide.load.model.GlideUrl -import com.bumptech.glide.load.model.LazyHeaders import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.target.BitmapImageViewTarget import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.InputStream -fun Context.bitmapCenterCrop(config: Config, url: String, iv: ImageView) = +fun Context.bitmapCenterCrop(url: String, iv: ImageView) = Glide.with(this) .asBitmap() - .loadMaybeBasicAuth(config, url) + .load(url) .apply(RequestOptions.centerCropTransform()) .into(iv) -fun Context.circularBitmapDrawable(config: Config, url: String, iv: ImageView) = +fun Context.circularBitmapDrawable(url: String, iv: ImageView) = Glide.with(this) .asBitmap() - .loadMaybeBasicAuth(config, url) + .load(url) .apply(RequestOptions.centerCropTransform()) .into(object : BitmapImageViewTarget(iv) { override fun setResource(resource: Bitmap?) { @@ -41,26 +34,6 @@ fun Context.circularBitmapDrawable(config: Config, url: String, iv: ImageView) = } }) -fun RequestBuilder.loadMaybeBasicAuth(config: Config, url: String): RequestBuilder { - val builder: LazyHeaders.Builder = LazyHeaders.Builder() - if (config.httpUserLogin.isNotEmpty() || config.httpUserPassword.isNotEmpty()) { - val basicAuth = "Basic " + Base64.encodeToString("${config.httpUserLogin}:${config.httpUserPassword}".toByteArray(), Base64.NO_WRAP) - builder.addHeader("Authorization", basicAuth) - } - val glideUrl = GlideUrl(url, builder.build()) - return this.load(glideUrl) -} - -fun RequestManager.loadMaybeBasicAuth(config: Config, url: String): RequestBuilder { - val builder: LazyHeaders.Builder = LazyHeaders.Builder() - if (config.httpUserLogin.isNotEmpty() || config.httpUserPassword.isNotEmpty()) { - val basicAuth = "Basic " + Base64.encodeToString("${config.httpUserLogin}:${config.httpUserPassword}".toByteArray(), Base64.NO_WRAP) - builder.addHeader("Authorization", basicAuth) - } - val glideUrl = GlideUrl(url, builder.build()) - return this.load(glideUrl) -} - fun getBitmapInputStream(bitmap:Bitmap,compressFormat: Bitmap.CompressFormat): InputStream { val byteArrayOutputStream = ByteArrayOutputStream() bitmap.compress(compressFormat, 80, byteArrayOutputStream) 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 064ff81..7eee3b2 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 @@ -1,18 +1,21 @@ package bou.amine.apps.readerforselfossv2.utils import android.text.format.DateUtils +import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import java.time.Instant import java.time.LocalDateTime import java.time.OffsetDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter -actual class DateUtils actual constructor(private val apiMajorVersion: Int) { +actual class DateUtils actual constructor(apiDetailsService: ApiDetailsService) { + val ads: ApiDetailsService = apiDetailsService // TODO: why is this needed now ? + actual fun parseDate(dateString: String): Long { val FORMATTERV1 = "yyyy-MM-dd HH:mm:ss" - return if (apiMajorVersion >= 4) { + return if (ads.getApiVersion() >= 4) { OffsetDateTime.parse(dateString).toInstant().toEpochMilli() } else { LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant( diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/DI/modules.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/DI/modules.kt index 2beba39..ebcb8f1 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/DI/modules.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/DI/modules.kt @@ -2,13 +2,12 @@ package bou.amine.apps.readerforselfossv2.DI import bou.amine.apps.readerforselfossv2.rest.SelfossApi import bou.amine.apps.readerforselfossv2.service.ApiDetailsService -import bou.amine.apps.readerforselfossv2.service.ApiDetailsServiceImpl import org.kodein.di.DI import org.kodein.di.bind import org.kodein.di.instance import org.kodein.di.singleton val networkModule by DI.Module { - bind() with singleton { ApiDetailsServiceImpl() } + bind() with singleton { ApiDetailsService() } bind() with singleton { SelfossApi(instance()) } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index cc8a878..d9e4254 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -32,7 +32,6 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails var itemsCaching = settings.getBoolean("items_caching", false) var offlineOverride = false - var apiMajorVersion = 0 var badgeUnread = 0 set(value) {field = if (value < 0) { 0 } else { value } } var badgeAll = 0 @@ -44,6 +43,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails // TODO: Dispatchers.IO not available in KMM, an alternative solution should be found CoroutineScope(Dispatchers.Main).launch { updateApiVersion() + dateUtils = DateUtils(apiDetails) reloadBadges() } } @@ -54,7 +54,6 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails if (isNetworkAvailable()) { fetchedItems = api.getItems( displayedItems.type, - settings.getString("prefer_api_items_number", "200").toInt(), offset = 0, tagFilter?.tag, sourceFilter?.id?.toLong(), @@ -84,7 +83,6 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails val offset = items.size fetchedItems = api.getItems( displayedItems.type, - settings.getString("prefer_api_items_number", "200").toInt(), offset, tagFilter?.tag, sourceFilter?.id?.toLong(), @@ -104,12 +102,12 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails return if (isNetworkAvailable()) { api.getItems( itemType.type, - 200, 0, tagFilter?.tag, sourceFilter?.id?.toLong(), searchFilter, - null + null, + 200 ) } else { emptyList() @@ -309,7 +307,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails spout, tags, filter, - apiMajorVersion + apiDetails.getApiVersion() )?.isSuccess == true } @@ -366,13 +364,13 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } private suspend fun updateApiVersion() { - apiMajorVersion = settings.getInt("apiVersionMajor", 0) + val apiMajorVersion = apiDetails.getApiVersion() if (isNetworkAvailable()) { val fetchedVersion = api.version() - if (fetchedVersion != null) { - apiMajorVersion = fetchedVersion.getApiMajorVersion() - settings.putInt("apiVersionMajor", apiMajorVersion) + if (fetchedVersion != null && fetchedVersion.getApiMajorVersion() != apiMajorVersion) { + settings.putInt("apiVersionMajor", fetchedVersion.getApiMajorVersion()) + apiDetails.refreshApiVersion() } } dateUtils = DateUtils(apiMajorVersion) 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 084d987..1a2c56e 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 @@ -4,6 +4,7 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import io.ktor.client.* import io.ktor.client.call.* +import io.ktor.client.plugins.* import io.ktor.client.plugins.cache.* import io.ktor.client.request.* import io.ktor.client.request.forms.* @@ -35,6 +36,9 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) { } level = LogLevel.ALL } + install(HttpTimeout) { + requestTimeoutMillis = apiDetailsService.getApiTimeout() + } /* TODO: Auth as basic if (apiDetailsService.getUserName().isNotEmpty() && apiDetailsService.getPassword().isNotEmpty()) { @@ -69,12 +73,12 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) { suspend fun getItems( type: String, - items: Int, offset: Int, tag: String?, source: Long?, search: String?, - updatedSince: String? + updatedSince: String?, + items: Int? = null ): List? = client.get(url("/items")) { parameter("username", apiDetailsService.getUserName()) @@ -84,7 +88,7 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) { parameter("source", source) parameter("search", search) parameter("updatedsince", updatedSince) - parameter("items", items) + parameter("items", items ?: apiDetailsService.getItemsNumber()) parameter("offset", offset) }.body() diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ApiDetailsService.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ApiDetailsService.kt index 48e477e..6a48792 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ApiDetailsService.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ApiDetailsService.kt @@ -1,10 +1,97 @@ package bou.amine.apps.readerforselfossv2.service -interface ApiDetailsService { - fun logApiCalls(message: String) - fun getApiVersion(): Int - fun getBaseUrl(): String - fun getUserName(): String - fun getPassword(): String - fun refresh() +import com.russhwolf.settings.Settings +import io.github.aakira.napier.Napier +import io.ktor.client.plugins.* + +class ApiDetailsService { + val settings: Settings = Settings() + private var _apiVersion: Int = -1 + private var _baseUrl: String = "" + private var _userName: String = "" + private var _password: String = "" + private var _itemsNumber: Int? = null + private var _apiTimeout: Long? = null + + fun logApiCalls(message: String) { + Napier.d(message, tag = "LogApiCalls") + } + + + fun getApiVersion(): Int { + if (_apiVersion == -1) { + refreshApiVersion() + return _apiVersion + } + return _apiVersion + } + + fun refreshApiVersion() { + _apiVersion = settings.getInt("apiVersionMajor", -1) + } + + fun getBaseUrl(): String { + if (_baseUrl.isEmpty()) { + _baseUrl = settings.getString("url", "") + } + return _baseUrl + } + + fun getUserName(): String { + if (_userName.isEmpty()) { + _userName = settings.getString("login", "") + } + return _userName + } + + fun getPassword(): String { + if (_password.isEmpty()) { + _password = settings.getString("password", "") + } + return _password + } + + fun getItemsNumber(): Int { + if (_itemsNumber == null) { + refreshItemsNumber() + } + return _itemsNumber!! + } + + fun refreshItemsNumber() { + _itemsNumber = settings.getString("prefer_api_items_number", "200").toInt() + } + + fun getApiTimeout(): Long { + if (_apiTimeout == null) { + refreshApiTimeout() + } + return _apiTimeout!! + } + + fun refreshApiTimeout() { + val settingsTimeout = settings.getLong("api_timeout", HttpTimeout.INFINITE_TIMEOUT_MS) + _apiTimeout = if (settingsTimeout > 0) settingsTimeout else HttpTimeout.INFINITE_TIMEOUT_MS + } + + fun refresh() { + _password = settings.getString("password", "") + _userName = settings.getString("login", "") + _baseUrl = settings.getString("url", "") + refreshApiVersion() + refreshItemsNumber() + refreshApiTimeout() + } + + companion object { + const val translationUrl = "https://crwd.in/readerforselfoss" + + const val sourceUrl = "https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform" + + const val trackerUrl = "https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues" + + const val syncChannelId = "sync-channel-id" + + const val newItemsChannelId = "new-items-channel-id" + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ApiDetailsServiceImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ApiDetailsServiceImpl.kt deleted file mode 100644 index 6256094..0000000 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ApiDetailsServiceImpl.kt +++ /dev/null @@ -1,52 +0,0 @@ -package bou.amine.apps.readerforselfossv2.service - -import com.russhwolf.settings.Settings -import io.github.aakira.napier.Napier - -class ApiDetailsServiceImpl : ApiDetailsService { - val settings: Settings = Settings() - private var _apiVersion: Int = -1 - private var _baseUrl: String = "" - private var _userName: String = "" - private var _password: String = "" - override fun logApiCalls(message: String) { - Napier.d(message, tag = "LogApiCalls") - } - - - override fun getApiVersion(): Int { - if (_apiVersion == -1) { - _apiVersion = settings.getInt("apiVersionMajor", -1) - return _apiVersion - } - return _apiVersion - } - - override fun getBaseUrl(): String { - if (_baseUrl.isEmpty()) { - _baseUrl = settings.getString("url", "") - } - return _baseUrl - } - - override fun getUserName(): String { - if (_userName.isEmpty()) { - _userName = settings.getString("login", "") - } - return _userName - } - - override fun getPassword(): String { - if (_password.isEmpty()) { - _password = settings.getString("password", "") - } - return _password - } - - override fun refresh() { - _password = settings.getString("password", "") - _userName = settings.getString("login", "") - _baseUrl = settings.getString("url", "") - _apiVersion = settings.getInt("apiVersionMajor", -1) - } -} \ No newline at end of file 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 95518ba..41ae126 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 @@ -1,12 +1,13 @@ package bou.amine.apps.readerforselfossv2.utils import bou.amine.apps.readerforselfossv2.model.SelfossModel +import bou.amine.apps.readerforselfossv2.service.ApiDetailsService fun SelfossModel.Item.parseDate(dateUtils: DateUtils): Long = dateUtils.parseDate(this.datetime) -expect class DateUtils(apiMajorVersion: Int) { +expect class DateUtils(apiDetailsService: ApiDetailsService) { fun parseDate(dateString: String): Long fun parseRelativeDate(dateString: String): String 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 69a287b..ec6fc1f 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,6 +1,8 @@ package bou.amine.apps.readerforselfossv2.utils -actual class DateUtils actual constructor(apiMajorVersion: Int) { +import bou.amine.apps.readerforselfossv2.service.ApiDetailsService + +actual class DateUtils actual constructor(apiDetailsService: ApiDetailsService) { actual fun parseDate(dateString: String): Long { TODO("Not yet implemented") } 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 69a287b..ec6fc1f 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,6 +1,8 @@ package bou.amine.apps.readerforselfossv2.utils -actual class DateUtils actual constructor(apiMajorVersion: Int) { +import bou.amine.apps.readerforselfossv2.service.ApiDetailsService + +actual class DateUtils actual constructor(apiDetailsService: ApiDetailsService) { actual fun parseDate(dateString: String): Long { TODO("Not yet implemented") }