Compare commits
1 Commits
6b5f6cbbe0
...
4fbebf2954
Author | SHA1 | Date | |
---|---|---|---|
4fbebf2954 |
@ -3,25 +3,34 @@ root = true
|
|||||||
[*]
|
[*]
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[{*.kt,*.kts}]
|
[.editorconfig]
|
||||||
|
insert_final_newline = false
|
||||||
|
ij_kotlin_line_break_after_multiline_when_entry = false
|
||||||
|
|
||||||
[*.{kt,kts}]
|
[*.{kt,kts}]
|
||||||
# Disable wildcard imports entirely
|
# Disable wildcard imports entirely
|
||||||
ij_kotlin_name_count_to_use_star_import = 2147483647
|
ij_kotlin_name_count_to_use_star_import = 2147483647
|
||||||
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
||||||
|
|
||||||
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
|
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
ij_kotlin_allow_trailing_comma = true
|
ij_kotlin_allow_trailing_comma = true
|
||||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||||
ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
|
ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
|
||||||
|
ij_kotlin_indent_before_arrow_on_new_line = false
|
||||||
|
ij_kotlin_line_break_after_multiline_when_entry = true
|
||||||
ij_kotlin_packages_to_use_import_on_demand = unset
|
ij_kotlin_packages_to_use_import_on_demand = unset
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
indent_style = space
|
indent_style = space
|
||||||
ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than = unset
|
ktlint_argument_list_wrapping_ignore_when_parameter_count_greater_or_equal_than = unset
|
||||||
|
ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than = 4
|
||||||
ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 1
|
ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 1
|
||||||
ktlint_code_style = ktlint_official
|
ktlint_code_style = ktlint_official
|
||||||
|
ktlint_enum_entry_name_casing = upper_or_camel_cases
|
||||||
|
ktlint_function_naming_ignore_when_annotated_with = unset
|
||||||
ktlint_function_signature_body_expression_wrapping = multiline
|
ktlint_function_signature_body_expression_wrapping = multiline
|
||||||
ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 2
|
ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 2
|
||||||
ktlint_ignore_back_ticked_identifier = false
|
ktlint_ignore_back_ticked_identifier = false
|
||||||
|
ktlint_property_naming_constant_naming = screaming_snake_case
|
||||||
max_line_length = 140
|
max_line_length = 140
|
||||||
|
|
||||||
|
[**/build]
|
||||||
|
ktlint = disabled
|
@ -104,12 +104,14 @@ fun testAddSourceWithUrl(
|
|||||||
onView(withId(R.id.fab))
|
onView(withId(R.id.fab))
|
||||||
.perform(click())
|
.perform(click())
|
||||||
onView(withId(R.id.nameInput))
|
onView(withId(R.id.nameInput))
|
||||||
.perform(click()).perform(typeTextIntoFocusedView(sourceName))
|
.perform(click())
|
||||||
|
.perform(typeTextIntoFocusedView(sourceName))
|
||||||
onView(withId(R.id.sourceUri))
|
onView(withId(R.id.sourceUri))
|
||||||
.perform(click())
|
.perform(click())
|
||||||
.perform(typeTextIntoFocusedView(url))
|
.perform(typeTextIntoFocusedView(url))
|
||||||
onView(withId(R.id.tags))
|
onView(withId(R.id.tags))
|
||||||
.perform(click()).perform(typeTextIntoFocusedView("tag1,tag2,tag3"))
|
.perform(click())
|
||||||
|
.perform(typeTextIntoFocusedView("tag1,tag2,tag3"))
|
||||||
onView(withId(R.id.spoutsSpinner))
|
onView(withId(R.id.spoutsSpinner))
|
||||||
.perform(click())
|
.perform(click())
|
||||||
onData(hasToString("RSS Feed")).perform(click())
|
onData(hasToString("RSS Feed")).perform(click())
|
||||||
|
@ -49,9 +49,7 @@ fun withError(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPopupWindow(): Matcher<Root> {
|
fun isPopupWindow(): Matcher<Root> = isPlatformPopup()
|
||||||
return isPlatformPopup()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun withDrawable(
|
fun withDrawable(
|
||||||
@DrawableRes id: Int,
|
@DrawableRes id: Int,
|
||||||
@ -73,8 +71,8 @@ fun withDrawable(
|
|||||||
|
|
||||||
fun hasBottombarItemText(
|
fun hasBottombarItemText(
|
||||||
@StringRes id: Int,
|
@StringRes id: Int,
|
||||||
): Matcher<View>? {
|
): Matcher<View>? =
|
||||||
return allOf(
|
allOf(
|
||||||
withResourceName("fixed_bottom_navigation_icon"),
|
withResourceName("fixed_bottom_navigation_icon"),
|
||||||
withParent(
|
withParent(
|
||||||
allOf(
|
allOf(
|
||||||
@ -83,23 +81,21 @@ fun hasBottombarItemText(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
fun withSettingsCheckboxWidget(
|
fun withSettingsCheckboxWidget(
|
||||||
@StringRes id: Int,
|
@StringRes id: Int,
|
||||||
): Matcher<View>? {
|
): Matcher<View>? =
|
||||||
return allOf(
|
allOf(
|
||||||
withId(android.R.id.switch_widget),
|
withId(android.R.id.switch_widget),
|
||||||
withParent(
|
withParent(
|
||||||
withSettingsCheckboxFrame(id),
|
withSettingsCheckboxFrame(id),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
fun withSettingsCheckboxFrame(
|
fun withSettingsCheckboxFrame(
|
||||||
@StringRes id: Int,
|
@StringRes id: Int,
|
||||||
): Matcher<View>? {
|
): Matcher<View>? =
|
||||||
return allOf(
|
allOf(
|
||||||
withId(android.R.id.widget_frame),
|
withId(android.R.id.widget_frame),
|
||||||
hasSibling(
|
hasSibling(
|
||||||
allOf(
|
allOf(
|
||||||
@ -110,7 +106,6 @@ fun withSettingsCheckboxFrame(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
fun openMenu() {
|
fun openMenu() {
|
||||||
openActionBarOverflowOrOptionsMenu(
|
openActionBarOverflowOrOptionsMenu(
|
||||||
|
@ -36,25 +36,31 @@ class LoginActivityTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun registerIdlingResource() {
|
fun registerIdlingResource() {
|
||||||
IdlingRegistry.getInstance()
|
IdlingRegistry
|
||||||
|
.getInstance()
|
||||||
.register(CountingIdlingResourceSingleton.countingIdlingResource)
|
.register(CountingIdlingResourceSingleton.countingIdlingResource)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun unregisterIdlingResource() {
|
fun unregisterIdlingResource() {
|
||||||
IdlingRegistry.getInstance()
|
IdlingRegistry
|
||||||
|
.getInstance()
|
||||||
.unregister(CountingIdlingResourceSingleton.countingIdlingResource)
|
.unregister(CountingIdlingResourceSingleton.countingIdlingResource)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun viewIsInitialized() {
|
fun viewIsInitialized() {
|
||||||
onView(withId(R.id.urlView)).check(matches(isDisplayed()))
|
onView(withId(R.id.urlView)).check(matches(isDisplayed()))
|
||||||
onView(withId(R.id.selfSigned)).check(matches(isDisplayed())).check(matches(isNotChecked()))
|
onView(withId(R.id.selfSigned))
|
||||||
|
.check(matches(isDisplayed()))
|
||||||
|
.check(matches(isNotChecked()))
|
||||||
.check(
|
.check(
|
||||||
matches(isClickable()),
|
matches(isClickable()),
|
||||||
)
|
)
|
||||||
onView(withId(R.id.withLogin)).check(matches(isDisplayed()))
|
onView(withId(R.id.withLogin))
|
||||||
.check(matches(isNotChecked())).check(
|
.check(matches(isDisplayed()))
|
||||||
|
.check(matches(isNotChecked()))
|
||||||
|
.check(
|
||||||
matches(isClickable()),
|
matches(isClickable()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
|
|||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import bou.amine.apps.readerforselfossv2.utils.Enums.ItemType
|
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||||
import com.ashokvarma.bottomnavigation.BottomNavigationBar
|
import com.ashokvarma.bottomnavigation.BottomNavigationBar
|
||||||
import com.ashokvarma.bottomnavigation.BottomNavigationItem
|
import com.ashokvarma.bottomnavigation.BottomNavigationItem
|
||||||
import com.ashokvarma.bottomnavigation.TextBadgeItem
|
import com.ashokvarma.bottomnavigation.TextBadgeItem
|
||||||
@ -595,7 +595,7 @@ class HomeActivity :
|
|||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.issue_tracker -> {
|
R.id.issue_tracker -> {
|
||||||
baseContext.openUrlInBrowser(AppSettingsService.TRACKER_URL)
|
baseContext.openUrlInBrowser(AppSettingsService.BUG_URL)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,9 @@ class ImageActivity : AppCompatActivity() {
|
|||||||
return super.onOptionsItemSelected(item)
|
return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
|
private inner class ScreenSlidePagerAdapter(
|
||||||
|
fa: FragmentActivity,
|
||||||
|
) : FragmentStateAdapter(fa) {
|
||||||
override fun getItemCount(): Int = allImages.size
|
override fun getItemCount(): Int = allImages.size
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position])
|
override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position])
|
||||||
|
@ -134,9 +134,18 @@ class LoginActivity :
|
|||||||
binding.passwordView.error = null
|
binding.passwordView.error = null
|
||||||
|
|
||||||
// Store values at the time of the login attempt.
|
// Store values at the time of the login attempt.
|
||||||
val url = binding.urlView.text.toString().trim()
|
val url =
|
||||||
val login = binding.loginView.text.toString().trim()
|
binding.urlView.text
|
||||||
val password = binding.passwordView.text.toString().trim()
|
.toString()
|
||||||
|
.trim()
|
||||||
|
val login =
|
||||||
|
binding.loginView.text
|
||||||
|
.toString()
|
||||||
|
.trim()
|
||||||
|
val password =
|
||||||
|
binding.passwordView.text
|
||||||
|
.toString()
|
||||||
|
.trim()
|
||||||
|
|
||||||
failInvalidUrl(url)
|
failInvalidUrl(url)
|
||||||
failLoginDetails(password, login)
|
failLoginDetails(password, login)
|
||||||
@ -273,7 +282,7 @@ class LoginActivity :
|
|||||||
return when (item.itemId) {
|
return when (item.itemId) {
|
||||||
R.id.issue_tracker -> {
|
R.id.issue_tracker -> {
|
||||||
val browserIntent =
|
val browserIntent =
|
||||||
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.TRACKER_URL))
|
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.BUG_URL))
|
||||||
startActivity(browserIntent)
|
startActivity(browserIntent)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -283,7 +292,7 @@ class LoginActivity :
|
|||||||
.withAboutIconShown(true)
|
.withAboutIconShown(true)
|
||||||
.withAboutVersionShown(true)
|
.withAboutVersionShown(true)
|
||||||
.withAboutSpecial2("Bug reports")
|
.withAboutSpecial2("Bug reports")
|
||||||
.withAboutSpecial2Description(AppSettingsService.TRACKER_URL)
|
.withAboutSpecial2Description(AppSettingsService.BUG_URL)
|
||||||
.withAboutSpecial1("Project Page")
|
.withAboutSpecial1("Project Page")
|
||||||
.withAboutSpecial1Description(AppSettingsService.SOURCE_URL)
|
.withAboutSpecial1Description(AppSettingsService.SOURCE_URL)
|
||||||
.start(this)
|
.start(this)
|
||||||
|
@ -160,7 +160,7 @@ class MyApp :
|
|||||||
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
|
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
|
||||||
val newItemsChannelmChannel =
|
val newItemsChannelmChannel =
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
AppSettingsService.NEW_ITEMS_CHANNEL_ID,
|
AppSettingsService.NEW_ITEMS_CHANNEL,
|
||||||
newItemsChannelname,
|
newItemsChannelname,
|
||||||
newItemsChannelimportance,
|
newItemsChannelimportance,
|
||||||
)
|
)
|
||||||
|
@ -22,7 +22,9 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
class ReaderActivity : AppCompatActivity(), DIAware {
|
class ReaderActivity :
|
||||||
|
AppCompatActivity(),
|
||||||
|
DIAware {
|
||||||
private var currentItem: Int = 0
|
private var currentItem: Int = 0
|
||||||
|
|
||||||
private lateinit var toolbarMenu: Menu
|
private lateinit var toolbarMenu: Menu
|
||||||
@ -99,8 +101,9 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
|||||||
oldInstanceState.clear()
|
oldInstanceState.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) :
|
private inner class ScreenSlidePagerAdapter(
|
||||||
FragmentStateAdapter(fa) {
|
fa: FragmentActivity,
|
||||||
|
) : FragmentStateAdapter(fa) {
|
||||||
override fun getItemCount(): Int = allItems.size
|
override fun getItemCount(): Int = allItems.size
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment = ArticleFragment.newInstance(allItems[position])
|
override fun createFragment(position: Int): Fragment = ArticleFragment.newInstance(allItems[position])
|
||||||
@ -109,8 +112,8 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
|||||||
override fun onKeyDown(
|
override fun onKeyDown(
|
||||||
keyCode: Int,
|
keyCode: Int,
|
||||||
event: KeyEvent?,
|
event: KeyEvent?,
|
||||||
): Boolean {
|
): Boolean =
|
||||||
return when (keyCode) {
|
when (keyCode) {
|
||||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||||
val currentFragment =
|
val currentFragment =
|
||||||
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
||||||
@ -129,7 +132,6 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
|||||||
super.onKeyDown(keyCode, event)
|
super.onKeyDown(keyCode, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun alignmentMenu() {
|
private fun alignmentMenu() {
|
||||||
val showJustify = appSettingsService.getActiveAllignment() == AppSettingsService.ALIGN_LEFT
|
val showJustify = appSettingsService.getActiveAllignment() == AppSettingsService.ALIGN_LEFT
|
||||||
|
@ -18,7 +18,9 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
class SourcesActivity : AppCompatActivity(), DIAware {
|
class SourcesActivity :
|
||||||
|
AppCompatActivity(),
|
||||||
|
DIAware {
|
||||||
private lateinit var binding: ActivitySourcesBinding
|
private lateinit var binding: ActivitySourcesBinding
|
||||||
|
|
||||||
override val di by closestDI()
|
override val di by closestDI()
|
||||||
@ -68,7 +70,8 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
|||||||
binding.recyclerView.adapter = mAdapter
|
binding.recyclerView.adapter = mAdapter
|
||||||
mAdapter.notifyDataSetChanged()
|
mAdapter.notifyDataSetChanged()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast
|
||||||
|
.makeText(
|
||||||
this@SourcesActivity,
|
this@SourcesActivity,
|
||||||
R.string.cant_get_sources,
|
R.string.cant_get_sources,
|
||||||
Toast.LENGTH_SHORT,
|
Toast.LENGTH_SHORT,
|
||||||
|
@ -21,7 +21,9 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
class UpsertSourceActivity :
|
||||||
|
AppCompatActivity(),
|
||||||
|
DIAware {
|
||||||
private var existingSource: SelfossModel.SourceDetail? = null
|
private var existingSource: SelfossModel.SourceDetail? = null
|
||||||
private var mSpoutsValue: String? = null
|
private var mSpoutsValue: String? = null
|
||||||
|
|
||||||
@ -105,7 +107,8 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun handleSpoutFailure(networkIssue: Boolean = false) {
|
fun handleSpoutFailure(networkIssue: Boolean = false) {
|
||||||
Toast.makeText(
|
Toast
|
||||||
|
.makeText(
|
||||||
this@UpsertSourceActivity,
|
this@UpsertSourceActivity,
|
||||||
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
|
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
|
||||||
Toast.LENGTH_SHORT,
|
Toast.LENGTH_SHORT,
|
||||||
@ -192,7 +195,8 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
|||||||
if (successfullyAddedSource) {
|
if (successfullyAddedSource) {
|
||||||
finish()
|
finish()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast
|
||||||
|
.makeText(
|
||||||
this@UpsertSourceActivity,
|
this@UpsertSourceActivity,
|
||||||
R.string.cant_create_source,
|
R.string.cant_create_source,
|
||||||
Toast.LENGTH_SHORT,
|
Toast.LENGTH_SHORT,
|
||||||
|
@ -129,5 +129,7 @@ class ItemCardAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root)
|
inner class ViewHolder(
|
||||||
|
val binding: CardItemBinding,
|
||||||
|
) : RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
||||||
|
@ -73,5 +73,7 @@ class ItemListAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root)
|
inner class ViewHolder(
|
||||||
|
val binding: ListItemBinding,
|
||||||
|
) : RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
|
|||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import bou.amine.apps.readerforselfossv2.utils.Enums.ItemType
|
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -29,7 +29,8 @@ import org.kodein.di.instance
|
|||||||
class SourcesListAdapter(
|
class SourcesListAdapter(
|
||||||
private val app: Activity,
|
private val app: Activity,
|
||||||
private val items: ArrayList<SelfossModel.SourceDetail>,
|
private val items: ArrayList<SelfossModel.SourceDetail>,
|
||||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
|
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(),
|
||||||
|
DIAware {
|
||||||
private val c: Context = app.baseContext
|
private val c: Context = app.baseContext
|
||||||
private lateinit var binding: SourceListItemBinding
|
private lateinit var binding: SourceListItemBinding
|
||||||
|
|
||||||
@ -61,7 +62,8 @@ class SourcesListAdapter(
|
|||||||
notifyItemRemoved(position)
|
notifyItemRemoved(position)
|
||||||
notifyItemRangeChanged(position, itemCount)
|
notifyItemRangeChanged(position, itemCount)
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast
|
||||||
|
.makeText(
|
||||||
app,
|
app,
|
||||||
R.string.can_delete_source,
|
R.string.can_delete_source,
|
||||||
Toast.LENGTH_SHORT,
|
Toast.LENGTH_SHORT,
|
||||||
@ -99,5 +101,7 @@ class SourcesListAdapter(
|
|||||||
|
|
||||||
override fun getItemCount(): Int = items.size
|
override fun getItemCount(): Int = items.size
|
||||||
|
|
||||||
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView)
|
inner class ViewHolder(
|
||||||
|
val mView: ConstraintLayout,
|
||||||
|
) : RecyclerView.ViewHolder(mView)
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ class LoadingWorker(
|
|||||||
NotificationCompat
|
NotificationCompat
|
||||||
.Builder(
|
.Builder(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
AppSettingsService.NEW_ITEMS_CHANNEL_ID,
|
AppSettingsService.NEW_ITEMS_CHANNEL,
|
||||||
).setContentTitle(context.getString(R.string.new_items_notification_title))
|
).setContentTitle(context.getString(R.string.new_items_notification_title))
|
||||||
.setContentText(
|
.setContentText(
|
||||||
context.getString(
|
context.getString(
|
||||||
@ -101,7 +101,7 @@ class LoadingWorker(
|
|||||||
newSize,
|
newSize,
|
||||||
),
|
),
|
||||||
).setPriority(PRIORITY_DEFAULT)
|
).setPriority(PRIORITY_DEFAULT)
|
||||||
.setChannelId(AppSettingsService.NEW_ITEMS_CHANNEL_ID)
|
.setChannelId(AppSettingsService.NEW_ITEMS_CHANNEL)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
||||||
|
@ -66,7 +66,9 @@ import java.util.concurrent.ExecutionException
|
|||||||
|
|
||||||
private const val IMAGE_JPG = "image/jpg"
|
private const val IMAGE_JPG = "image/jpg"
|
||||||
|
|
||||||
class ArticleFragment : Fragment(), DIAware {
|
class ArticleFragment :
|
||||||
|
Fragment(),
|
||||||
|
DIAware {
|
||||||
private var fontSize: Int = 16
|
private var fontSize: Int = 16
|
||||||
private lateinit var item: SelfossModel.Item
|
private lateinit var item: SelfossModel.Item
|
||||||
private lateinit var url: String
|
private lateinit var url: String
|
||||||
@ -167,7 +169,8 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
} catch (e: InflateException) {
|
} catch (e: InflateException) {
|
||||||
e.sendSilentlyWithAcraWithName("webview not available")
|
e.sendSilentlyWithAcraWithName("webview not available")
|
||||||
try {
|
try {
|
||||||
AlertDialog.Builder(requireContext())
|
AlertDialog
|
||||||
|
.Builder(requireContext())
|
||||||
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
||||||
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
||||||
.setPositiveButton(
|
.setPositiveButton(
|
||||||
@ -175,8 +178,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
) { _, _ ->
|
) { _, _ ->
|
||||||
appSettingsService.disableArticleViewer()
|
appSettingsService.disableArticleViewer()
|
||||||
requireActivity().finish()
|
requireActivity().finish()
|
||||||
}
|
}.create()
|
||||||
.create()
|
|
||||||
.show()
|
.show()
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
e.sendSilentlyWithAcraWithName("Context required is null")
|
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||||
@ -235,7 +237,8 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
repository.markAsRead(this@ArticleFragment.item)
|
repository.markAsRead(this@ArticleFragment.item)
|
||||||
}
|
}
|
||||||
this@ArticleFragment.item.unread = false
|
this@ArticleFragment.item.unread = false
|
||||||
Toast.makeText(
|
Toast
|
||||||
|
.makeText(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
R.string.marked_as_read,
|
R.string.marked_as_read,
|
||||||
Toast.LENGTH_LONG,
|
Toast.LENGTH_LONG,
|
||||||
@ -245,7 +248,8 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
repository.unmarkAsRead(this@ArticleFragment.item)
|
repository.unmarkAsRead(this@ArticleFragment.item)
|
||||||
}
|
}
|
||||||
this@ArticleFragment.item.unread = true
|
this@ArticleFragment.item.unread = true
|
||||||
Toast.makeText(
|
Toast
|
||||||
|
.makeText(
|
||||||
context,
|
context,
|
||||||
R.string.marked_as_unread,
|
R.string.marked_as_unread,
|
||||||
Toast.LENGTH_LONG,
|
Toast.LENGTH_LONG,
|
||||||
@ -322,8 +326,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(
|
.load(
|
||||||
lead_image_url,
|
lead_image_url,
|
||||||
)
|
).apply(RequestOptions.fitCenterTransform())
|
||||||
.apply(RequestOptions.fitCenterTransform())
|
|
||||||
.into(binding.imageView)
|
.into(binding.imageView)
|
||||||
} else {
|
} else {
|
||||||
binding.imageView.visibility = View.GONE
|
binding.imageView.visibility = View.GONE
|
||||||
@ -337,8 +340,8 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
override fun shouldOverrideUrlLoading(
|
override fun shouldOverrideUrlLoading(
|
||||||
view: WebView?,
|
view: WebView?,
|
||||||
url: String,
|
url: String,
|
||||||
): Boolean {
|
): Boolean =
|
||||||
return if (context != null &&
|
if (context != null &&
|
||||||
url.isUrlValid() &&
|
url.isUrlValid() &&
|
||||||
binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
||||||
) {
|
) {
|
||||||
@ -347,7 +350,6 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
@Deprecated("Deprecated in Java")
|
||||||
override fun shouldInterceptRequest(
|
override fun shouldInterceptRequest(
|
||||||
@ -356,12 +358,18 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
): WebResourceResponse? {
|
): WebResourceResponse? {
|
||||||
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||||
if (url.lowercase(Locale.US).contains(".jpg") ||
|
if (url.lowercase(Locale.US).contains(".jpg") ||
|
||||||
url.lowercase(Locale.US)
|
url
|
||||||
|
.lowercase(Locale.US)
|
||||||
.contains(".jpeg")
|
.contains(".jpeg")
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
val image =
|
val image =
|
||||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
Glide
|
||||||
|
.with(view)
|
||||||
|
.asBitmap()
|
||||||
|
.apply(glideOptions)
|
||||||
|
.load(url)
|
||||||
|
.submit()
|
||||||
.get()
|
.get()
|
||||||
return WebResourceResponse(
|
return WebResourceResponse(
|
||||||
IMAGE_JPG,
|
IMAGE_JPG,
|
||||||
@ -374,7 +382,12 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
} else if (url.lowercase(Locale.US).contains(".png")) {
|
} else if (url.lowercase(Locale.US).contains(".png")) {
|
||||||
try {
|
try {
|
||||||
val image =
|
val image =
|
||||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
Glide
|
||||||
|
.with(view)
|
||||||
|
.asBitmap()
|
||||||
|
.apply(glideOptions)
|
||||||
|
.load(url)
|
||||||
|
.submit()
|
||||||
.get()
|
.get()
|
||||||
return WebResourceResponse(
|
return WebResourceResponse(
|
||||||
IMAGE_JPG,
|
IMAGE_JPG,
|
||||||
@ -387,7 +400,12 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
||||||
try {
|
try {
|
||||||
val image =
|
val image =
|
||||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
Glide
|
||||||
|
.with(view)
|
||||||
|
.asBitmap()
|
||||||
|
.apply(glideOptions)
|
||||||
|
.load(url)
|
||||||
|
.submit()
|
||||||
.get()
|
.get()
|
||||||
return WebResourceResponse(
|
return WebResourceResponse(
|
||||||
IMAGE_JPG,
|
IMAGE_JPG,
|
||||||
@ -453,9 +471,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
activity,
|
activity,
|
||||||
object : GestureDetector.SimpleOnGestureListener() {
|
object : GestureDetector.SimpleOnGestureListener() {
|
||||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
override fun onSingleTapUp(e: MotionEvent): Boolean = performClick()
|
||||||
return performClick()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -600,7 +616,8 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun performClick(): Boolean {
|
fun performClick(): Boolean {
|
||||||
if (allImages != null && (
|
if (allImages != null &&
|
||||||
|
(
|
||||||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
|
binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
|
||||||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
||||||
)
|
)
|
||||||
|
@ -33,7 +33,9 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.x.closestDI
|
import org.kodein.di.android.x.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
class FilterSheetFragment :
|
||||||
|
BottomSheetDialogFragment(),
|
||||||
|
DIAware {
|
||||||
private lateinit var binding: FilterFragmentBinding
|
private lateinit var binding: FilterFragmentBinding
|
||||||
override val di: DI by closestDI()
|
override val di: DI by closestDI()
|
||||||
private val repository: Repository by instance()
|
private val repository: Repository by instance()
|
||||||
@ -80,7 +82,8 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
|||||||
val c = Chip(context)
|
val c = Chip(context)
|
||||||
c.ellipsize = TextUtils.TruncateAt.END
|
c.ellipsize = TextUtils.TruncateAt.END
|
||||||
|
|
||||||
Glide.with(context)
|
Glide
|
||||||
|
.with(context)
|
||||||
.load(source.getIcon(repository.baseUrl))
|
.load(source.getIcon(repository.baseUrl))
|
||||||
.into(
|
.into(
|
||||||
object : ViewTarget<Chip?, Drawable?>(c) {
|
object : ViewTarget<Chip?, Drawable?>(c) {
|
||||||
|
@ -14,7 +14,7 @@ class ImageFragment : Fragment() {
|
|||||||
private lateinit var imageUrl: String
|
private lateinit var imageUrl: String
|
||||||
private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||||
private var _binding: FragmentImageBinding? = null
|
private var _binding: FragmentImageBinding? = null
|
||||||
private val binding get() = _binding
|
val binding get() = _binding
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -31,7 +31,8 @@ class ImageFragment : Fragment() {
|
|||||||
val view = binding?.root
|
val view = binding?.root
|
||||||
|
|
||||||
binding!!.photoView.visibility = View.VISIBLE
|
binding!!.photoView.visibility = View.VISIBLE
|
||||||
Glide.with(requireActivity())
|
Glide
|
||||||
|
.with(requireActivity())
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.apply(glideOptions)
|
.apply(glideOptions)
|
||||||
.load(imageUrl)
|
.load(imageUrl)
|
||||||
|
@ -17,9 +17,12 @@ fun SelfossModel.Item.preloadImages(context: Context): Boolean {
|
|||||||
try {
|
try {
|
||||||
for (url in imageUrls) {
|
for (url in imageUrls) {
|
||||||
if (URLUtil.isValidUrl(url)) {
|
if (URLUtil.isValidUrl(url)) {
|
||||||
Glide.with(context).asBitmap()
|
Glide
|
||||||
|
.with(context)
|
||||||
|
.asBitmap()
|
||||||
.apply(glideOptions)
|
.apply(glideOptions)
|
||||||
.load(url).submit()
|
.load(url)
|
||||||
|
.submit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
|
@ -236,7 +236,7 @@ class SettingsActivity :
|
|||||||
|
|
||||||
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener =
|
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
openUrl(AppSettingsService.TRACKER_URL)
|
openUrl(AppSettingsService.BUG_URL)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,8 @@ fun Context.shareLink(
|
|||||||
sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle)
|
sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle)
|
||||||
sendIntent.type = "text/plain"
|
sendIntent.type = "text/plain"
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent.createChooser(
|
Intent
|
||||||
|
.createChooser(
|
||||||
sendIntent,
|
sendIntent,
|
||||||
getString(R.string.share),
|
getString(R.string.share),
|
||||||
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||||
|
@ -59,7 +59,5 @@ class CircleImageView
|
|||||||
textView.text = text.toTextDrawableString()
|
textView.text = text.toTextDrawableString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun colorFromIdentifier(key: String): Int {
|
private fun colorFromIdentifier(key: String): Int = colorScheme[abs(key.hashCode()) % colorScheme.size]
|
||||||
return colorScheme[abs(key.hashCode()) % colorScheme.size]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,8 @@ fun Context.openItemUrl(
|
|||||||
app: Activity,
|
app: Activity,
|
||||||
) {
|
) {
|
||||||
if (!linkDecoded.isUrlValid()) {
|
if (!linkDecoded.isUrlValid()) {
|
||||||
Toast.makeText(
|
Toast
|
||||||
|
.makeText(
|
||||||
this,
|
this,
|
||||||
this.getString(R.string.cant_open_invalid_url),
|
this.getString(R.string.cant_open_invalid_url),
|
||||||
Toast.LENGTH_LONG,
|
Toast.LENGTH_LONG,
|
||||||
|
@ -13,7 +13,8 @@ import java.io.InputStream
|
|||||||
fun Context.bitmapCenterCrop(
|
fun Context.bitmapCenterCrop(
|
||||||
url: String,
|
url: String,
|
||||||
iv: ImageView,
|
iv: ImageView,
|
||||||
) = Glide.with(this)
|
) = Glide
|
||||||
|
.with(this)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(url)
|
.load(url)
|
||||||
.apply(RequestOptions.centerCropTransform())
|
.apply(RequestOptions.centerCropTransform())
|
||||||
@ -25,7 +26,8 @@ fun Context.circularDrawable(
|
|||||||
) {
|
) {
|
||||||
view.textView.text = ""
|
view.textView.text = ""
|
||||||
|
|
||||||
Glide.with(this)
|
Glide
|
||||||
|
.with(this)
|
||||||
.load(url)
|
.load(url)
|
||||||
.into(view.imageView)
|
.into(view.imageView)
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,9 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
|||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class AppViewModel(private val repository: Repository) : ViewModel() {
|
class AppViewModel(
|
||||||
|
private val repository: Repository,
|
||||||
|
) : ViewModel() {
|
||||||
private val _networkAvailableProvider = MutableSharedFlow<Boolean>()
|
private val _networkAvailableProvider = MutableSharedFlow<Boolean>()
|
||||||
val networkAvailableProvider = _networkAvailableProvider.asSharedFlow()
|
val networkAvailableProvider = _networkAvailableProvider.asSharedFlow()
|
||||||
private var wasConnected = true
|
private var wasConnected = true
|
||||||
|
@ -42,15 +42,14 @@ private const val FEED_URL = "https://test.com/feed"
|
|||||||
|
|
||||||
private const val TAGS = "Test, New"
|
private const val TAGS = "Test, New"
|
||||||
|
|
||||||
private const val NUMBER_ARTICLES = 100
|
private val NUMBER_ARTICLES = 100
|
||||||
private const val NUMBER_UNREAD = 50
|
private val NUMBER_UNREAD = 50
|
||||||
private const val NUMBER_STARRED = 20
|
private val NUMBER_STARRED = 20
|
||||||
|
|
||||||
class RepositoryTest {
|
class RepositoryTest {
|
||||||
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
|
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
|
||||||
private val appSettingsService = mockk<AppSettingsService>()
|
private val appSettingsService = mockk<AppSettingsService>()
|
||||||
private val api = mockk<SelfossApi>()
|
private val api = mockk<SelfossApi>()
|
||||||
|
|
||||||
private lateinit var repository: Repository
|
private lateinit var repository: Repository
|
||||||
|
|
||||||
private fun initializeRepository(
|
private fun initializeRepository(
|
||||||
|
@ -44,7 +44,7 @@ class FakeItemParameters {
|
|||||||
var datetime = "2022-09-09T03:32:01-04:00"
|
var datetime = "2022-09-09T03:32:01-04:00"
|
||||||
val title = "Etica della ricerca sotto i riflettori."
|
val title = "Etica della ricerca sotto i riflettori."
|
||||||
val content =
|
val content =
|
||||||
"<p><strong>Luigi Campanella, già Presidente SCI</strong></p>\n<p>L’etica della scienza è di certo ambito di cui continuiamo</p>"
|
"<p><strong>Luigi Campanella, già Presidente SCI</strong></p>\n<p>L’etica della scienza è di certo ambito di cui continuiamo a scoprire nuovi aspetti e risvolti.</p>\n<p>L’ultimo è quello delle intelligenze artificiali capaci di creare opere complesse basate su immagini e parole memorizzate con il rischio di fake news e di contenuti disturbanti.</p>\n<p>Per evitare che ciò accada si sta procedendo filtrando secondo criteri di autocensura i dati da cui l’intelligenza artificiale parte.</p>\n<p>Comincia ad intravedersi un futuro prossimo di competizione fra autori umani ed artificiali nel quale sarà importante, quando i loro prodotti saranno indistinguibili, dichiararne l’origine.</p>\n<p>Come si comprende, si conferma che gli aspetti etici dell’innovazione e della ricerca si diversificato sempre di più.</p>\n<p>La biologia molecolare e la genetica già in passato hanno posto all’attenzione comune aspetti di etica della scienza che hanno indotto a nuove riflessioni circa i limiti delle ricerche.</p>\n<p>L’argomento, sempre attuale, torna sulle prime pagine a seguito della pubblicazione di una ricerca della Università di Cambridge che ha sviluppato una struttura cellulare di un topo con un cuore che batte regolarmente.</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image002-1.png?w=481\" alt=\"\" width=\"697\" height=\"430\" /><img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image003-1.png?w=906\" alt=\"\" /><p>Magdalena Zernicka-Goetz</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image004.jpg?w=474\" alt=\"\" width=\"622\" height=\"465\" /><p>Gianluca Amadei</p>\n<p>Del gruppo fa parte anche uno scienziato italiano Gianluca Amadei,che dinnanzi alle obiezioni di natura etica sulla realizzazione della vita artificiale si è affrettato a sostenere che non è creare nuove vite il fine primario della ricerca, ma quello di salvare quelle esistenti, di dare contributi essenziali alla medicina citando il caso del fallimento tuttora non interpretato di alcune gravidanze e di superare la sperimentazione animale, così contribuendo positivamente alla soluzione di un altro dilemma etico.</p>\n<p>L’embrione sintetico ha ovviamente come primo traguardo il contributo ai trapianti oggi drammaticamente carenti nell’offerta rispetto alla domanda, con attese fino a 4 anni per i trapianti di cuore ed a 2 anni per quelli di fegato. Il lavoro dovrebbe adesso continuare presso l’Ateneo di Padova per creare nuovi organi e nuovi farmaci.</p>"
|
||||||
var unread = true
|
var unread = true
|
||||||
var starred = true
|
var starred = true
|
||||||
val thumbnail = null
|
val thumbnail = null
|
||||||
|
@ -4,12 +4,13 @@ import android.content.Context
|
|||||||
import app.cash.sqldelight.db.SqlDriver
|
import app.cash.sqldelight.db.SqlDriver
|
||||||
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
||||||
|
|
||||||
actual class DriverFactory(private val context: Context) {
|
actual class DriverFactory(
|
||||||
actual fun createDriver(): SqlDriver {
|
private val context: Context,
|
||||||
return AndroidSqliteDriver(
|
) {
|
||||||
|
actual fun createDriver(): SqlDriver =
|
||||||
|
AndroidSqliteDriver(
|
||||||
ReaderForSelfossDB.Schema,
|
ReaderForSelfossDB.Schema,
|
||||||
context,
|
context,
|
||||||
"ReaderForSelfossV2-android.db",
|
"ReaderForSelfossV2-android.db",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -7,7 +7,7 @@ class MercuryModel {
|
|||||||
class ParsedContent(
|
class ParsedContent(
|
||||||
val title: String? = null,
|
val title: String? = null,
|
||||||
val content: String? = null,
|
val content: String? = null,
|
||||||
val lead_image_url: String? = null,
|
val lead_image_url: String? = null, // NOSONAR
|
||||||
val url: String? = null,
|
val url: String? = null,
|
||||||
val error: Boolean? = null,
|
val error: Boolean? = null,
|
||||||
val message: String? = null,
|
val message: String? = null,
|
||||||
|
@ -11,7 +11,7 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
|||||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import bou.amine.apps.readerforselfossv2.utils.Enums.ItemType
|
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||||
import bou.amine.apps.readerforselfossv2.utils.toEntity
|
import bou.amine.apps.readerforselfossv2.utils.toEntity
|
||||||
import bou.amine.apps.readerforselfossv2.utils.toParsedDate
|
import bou.amine.apps.readerforselfossv2.utils.toParsedDate
|
||||||
@ -36,26 +36,26 @@ class Repository(
|
|||||||
|
|
||||||
var displayedItems = ItemType.UNREAD
|
var displayedItems = ItemType.UNREAD
|
||||||
|
|
||||||
private var tagFilterFlow = MutableStateFlow<SelfossModel.Tag?>(null)
|
private var _tagFilter = MutableStateFlow<SelfossModel.Tag?>(null)
|
||||||
var tagFilter = tagFilterFlow.asStateFlow()
|
var tagFilter = _tagFilter.asStateFlow()
|
||||||
private var sourceFilterFlow = MutableStateFlow<SelfossModel.Source?>(null)
|
private var _sourceFilter = MutableStateFlow<SelfossModel.Source?>(null)
|
||||||
var sourceFilter = sourceFilterFlow.asStateFlow()
|
var sourceFilter = _sourceFilter.asStateFlow()
|
||||||
var searchFilter: String? = null
|
var searchFilter: String? = null
|
||||||
|
|
||||||
var offlineOverride = false
|
var offlineOverride = false
|
||||||
|
|
||||||
private val badgeUnreadFlow = MutableStateFlow(0)
|
private val _badgeUnread = MutableStateFlow(0)
|
||||||
val badgeUnread = badgeUnreadFlow.asStateFlow()
|
val badgeUnread = _badgeUnread.asStateFlow()
|
||||||
private val badgeAllFlow = MutableStateFlow(0)
|
private val _badgeAll = MutableStateFlow(0)
|
||||||
val badgeAll = badgeAllFlow.asStateFlow()
|
val badgeAll = _badgeAll.asStateFlow()
|
||||||
private val badgeStarredFlow = MutableStateFlow(0)
|
private val _badgeStarred = MutableStateFlow(0)
|
||||||
val badgeStarred = badgeStarredFlow.asStateFlow()
|
val badgeStarred = _badgeStarred.asStateFlow()
|
||||||
|
|
||||||
private var fetchedTags = false
|
private var fetchedTags = false
|
||||||
private var fetchedSources = false
|
private var fetchedSources = false
|
||||||
|
|
||||||
private var readerItems = ArrayList<SelfossModel.Item>()
|
private var _readerItems = ArrayList<SelfossModel.Item>()
|
||||||
private var selectedSource: SelfossModel.SourceDetail? = null
|
private var _selectedSource: SelfossModel.SourceDetail? = null
|
||||||
|
|
||||||
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
||||||
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
||||||
@ -144,17 +144,17 @@ class Repository(
|
|||||||
if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
val response = api.stats()
|
val response = api.stats()
|
||||||
if (response.success && response.data != null) {
|
if (response.success && response.data != null) {
|
||||||
badgeUnreadFlow.value = response.data.unread ?: 0
|
_badgeUnread.value = response.data.unread ?: 0
|
||||||
badgeAllFlow.value = response.data.total
|
_badgeAll.value = response.data.total
|
||||||
badgeStarredFlow.value = response.data.starred ?: 0
|
_badgeStarred.value = response.data.starred ?: 0
|
||||||
success = true
|
success = true
|
||||||
}
|
}
|
||||||
} else if (appSettingsService.isItemCachingEnabled()) {
|
} else if (appSettingsService.isItemCachingEnabled()) {
|
||||||
// TODO: do this differently, because it's not efficient
|
// TODO: do this differently, because it's not efficient
|
||||||
val dbItems = getDBItems()
|
val dbItems = getDBItems()
|
||||||
badgeUnreadFlow.value = dbItems.filter { item -> item.unread }.size
|
_badgeUnread.value = dbItems.filter { item -> item.unread }.size
|
||||||
badgeStarredFlow.value = dbItems.filter { item -> item.starred }.size
|
_badgeStarred.value = dbItems.filter { item -> item.starred }.size
|
||||||
badgeAllFlow.value = dbItems.size
|
_badgeAll.value = dbItems.size
|
||||||
success = true
|
success = true
|
||||||
}
|
}
|
||||||
return success
|
return success
|
||||||
@ -316,7 +316,7 @@ class Repository(
|
|||||||
private fun markAsReadLocally(item: SelfossModel.Item) {
|
private fun markAsReadLocally(item: SelfossModel.Item) {
|
||||||
if (item.unread) {
|
if (item.unread) {
|
||||||
item.unread = false
|
item.unread = false
|
||||||
badgeUnreadFlow.value -= 1
|
_badgeUnread.value -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@ -327,7 +327,7 @@ class Repository(
|
|||||||
private fun unmarkAsReadLocally(item: SelfossModel.Item) {
|
private fun unmarkAsReadLocally(item: SelfossModel.Item) {
|
||||||
if (!item.unread) {
|
if (!item.unread) {
|
||||||
item.unread = true
|
item.unread = true
|
||||||
badgeUnreadFlow.value += 1
|
_badgeUnread.value += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@ -338,7 +338,7 @@ class Repository(
|
|||||||
private fun starrLocally(item: SelfossModel.Item) {
|
private fun starrLocally(item: SelfossModel.Item) {
|
||||||
if (!item.starred) {
|
if (!item.starred) {
|
||||||
item.starred = true
|
item.starred = true
|
||||||
badgeStarredFlow.value += 1
|
_badgeStarred.value += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@ -349,7 +349,7 @@ class Repository(
|
|||||||
private fun unstarrLocally(item: SelfossModel.Item) {
|
private fun unstarrLocally(item: SelfossModel.Item) {
|
||||||
if (item.starred) {
|
if (item.starred) {
|
||||||
item.starred = false
|
item.starred = false
|
||||||
badgeStarredFlow.value -= 1
|
_badgeStarred.value -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@ -614,30 +614,30 @@ class Repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setTagFilter(tag: SelfossModel.Tag?) {
|
fun setTagFilter(tag: SelfossModel.Tag?) {
|
||||||
tagFilterFlow.value = tag
|
_tagFilter.value = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSourceFilter(source: SelfossModel.Source?) {
|
fun setSourceFilter(source: SelfossModel.Source?) {
|
||||||
sourceFilterFlow.value = source
|
_sourceFilter.value = source
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setReaderItems(readerItems: ArrayList<SelfossModel.Item>) {
|
fun setReaderItems(readerItems: ArrayList<SelfossModel.Item>) {
|
||||||
this.readerItems = readerItems
|
_readerItems = readerItems
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getReaderItems(): ArrayList<SelfossModel.Item> = readerItems
|
fun getReaderItems(): ArrayList<SelfossModel.Item> = _readerItems
|
||||||
|
|
||||||
fun migrate(driverFactory: DriverFactory) {
|
fun migrate(driverFactory: DriverFactory) {
|
||||||
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
|
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSelectedSource(source: SelfossModel.SourceDetail) {
|
fun setSelectedSource(source: SelfossModel.SourceDetail) {
|
||||||
selectedSource = source
|
_selectedSource = source
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unsetSelectedSource() {
|
fun unsetSelectedSource() {
|
||||||
selectedSource = null
|
_selectedSource = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSelectedSource(): SelfossModel.SourceDetail? = selectedSource
|
fun getSelectedSource(): SelfossModel.SourceDetail? = _selectedSource
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,8 @@ import kotlinx.serialization.json.Json
|
|||||||
class MercuryApi {
|
class MercuryApi {
|
||||||
var client = createHttpClient()
|
var client = createHttpClient()
|
||||||
|
|
||||||
private fun createHttpClient(): HttpClient {
|
private fun createHttpClient(): HttpClient =
|
||||||
return HttpClient {
|
HttpClient {
|
||||||
install(HttpCache)
|
install(HttpCache)
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json(
|
json(
|
||||||
@ -40,7 +40,6 @@ class MercuryApi {
|
|||||||
}
|
}
|
||||||
expectSuccess = false
|
expectSuccess = false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun query(url: String): StatusAndData<MercuryModel.ParsedContent> =
|
suspend fun query(url: String): StatusAndData<MercuryModel.ParsedContent> =
|
||||||
bodyOrFailure(
|
bodyOrFailure(
|
||||||
|
@ -484,11 +484,11 @@ class AppSettingsService(
|
|||||||
|
|
||||||
const val SOURCE_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform"
|
const val SOURCE_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform"
|
||||||
|
|
||||||
const val TRACKER_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
|
const val BUG_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
|
||||||
|
|
||||||
const val SYNC_CHANNEL_ID = "sync-channel-id"
|
const val SYNC_CHANNEL_ID = "sync-channel-id"
|
||||||
|
|
||||||
const val NEW_ITEMS_CHANNEL_ID = "new-items-channel-id"
|
const val NEW_ITEMS_CHANNEL = "new-items-channel-id"
|
||||||
|
|
||||||
const val JUSTIFY = 1
|
const val JUSTIFY = 1
|
||||||
|
|
||||||
|
@ -17,7 +17,11 @@ fun String.toParsedDate(): Long {
|
|||||||
if (this.matches(oldVersionFormat)) {
|
if (this.matches(oldVersionFormat)) {
|
||||||
this.replace(" ", "T")
|
this.replace(" ", "T")
|
||||||
} else if (this.matches(newVersionFormat)) {
|
} else if (this.matches(newVersionFormat)) {
|
||||||
newVersionFormat.find(this)?.groups?.get(1)?.value ?: throw Exception("Couldn't parse $this")
|
newVersionFormat
|
||||||
|
.find(this)
|
||||||
|
?.groups
|
||||||
|
?.get(1)
|
||||||
|
?.value ?: throw Exception("Couldn't parse $this")
|
||||||
} else {
|
} else {
|
||||||
throw Exception("Unrecognized format for $this")
|
throw Exception("Unrecognized format for $this")
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
|
||||||
|
|
||||||
object Enums {
|
|
||||||
enum class ItemType(
|
|
||||||
val position: Int,
|
|
||||||
val type: String,
|
|
||||||
) {
|
|
||||||
UNREAD(1, "unread"),
|
|
||||||
ALL(2, "all"),
|
|
||||||
STARRED(3, "starred"),
|
|
||||||
;
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun fromInt(value: Int) = values().first { it.position == value }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,15 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
|
enum class ItemType(
|
||||||
|
val position: Int,
|
||||||
|
val type: String,
|
||||||
|
) {
|
||||||
|
UNREAD(1, "unread"),
|
||||||
|
ALL(2, "all"),
|
||||||
|
STARRED(3, "starred"),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromInt(value: Int) = values().first { it.position == value }
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,8 @@ class DatesTest {
|
|||||||
fun new_version_date_should_be_parsed() {
|
fun new_version_date_should_be_parsed() {
|
||||||
val date = newVersionDate.toParsedDate()
|
val date = newVersionDate.toParsedDate()
|
||||||
val expected =
|
val expected =
|
||||||
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
LocalDateTime(2013, 4, 7, 13, 43, 0, 0)
|
||||||
|
.toInstant(TimeZone.currentSystemDefault())
|
||||||
.toEpochMilliseconds()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
||||||
@ -28,7 +29,8 @@ class DatesTest {
|
|||||||
fun new_version_date2_should_be_parsed() {
|
fun new_version_date2_should_be_parsed() {
|
||||||
val date = newVersionDate2.toParsedDate()
|
val date = newVersionDate2.toParsedDate()
|
||||||
val expected =
|
val expected =
|
||||||
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
LocalDateTime(2013, 4, 7, 13, 43, 0, 0)
|
||||||
|
.toInstant(TimeZone.currentSystemDefault())
|
||||||
.toEpochMilliseconds()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
||||||
@ -38,7 +40,8 @@ class DatesTest {
|
|||||||
fun old_version_date_should_be_parsed() {
|
fun old_version_date_should_be_parsed() {
|
||||||
val date = oldVersionDate.toParsedDate()
|
val date = oldVersionDate.toParsedDate()
|
||||||
val expected =
|
val expected =
|
||||||
LocalDateTime(2013, 5, 7, 13, 46, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
LocalDateTime(2013, 5, 7, 13, 46, 0, 0)
|
||||||
|
.toInstant(TimeZone.currentSystemDefault())
|
||||||
.toEpochMilliseconds()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
||||||
@ -48,7 +51,8 @@ class DatesTest {
|
|||||||
fun old_version_variant_date_should_be_parsed() {
|
fun old_version_variant_date_should_be_parsed() {
|
||||||
val date = oldVersionDateVariant.toParsedDate()
|
val date = oldVersionDateVariant.toParsedDate()
|
||||||
val expected =
|
val expected =
|
||||||
LocalDateTime(2021, 3, 21, 10, 32, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
LocalDateTime(2021, 3, 21, 10, 32, 0, 0)
|
||||||
|
.toInstant(TimeZone.currentSystemDefault())
|
||||||
.toEpochMilliseconds()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
||||||
@ -58,7 +62,8 @@ class DatesTest {
|
|||||||
fun new_version_variant_date_should_be_parsed() {
|
fun new_version_variant_date_should_be_parsed() {
|
||||||
val date = newVersionDateVariant.toParsedDate()
|
val date = newVersionDateVariant.toParsedDate()
|
||||||
val expected =
|
val expected =
|
||||||
LocalDateTime(2022, 12, 24, 17, 0, 8, 0).toInstant(TimeZone.currentSystemDefault())
|
LocalDateTime(2022, 12, 24, 17, 0, 8, 0)
|
||||||
|
.toInstant(TimeZone.currentSystemDefault())
|
||||||
.toEpochMilliseconds()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
||||||
|
@ -4,7 +4,5 @@ import app.cash.sqldelight.db.SqlDriver
|
|||||||
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
||||||
|
|
||||||
actual class DriverFactory {
|
actual class DriverFactory {
|
||||||
actual fun createDriver(): SqlDriver {
|
actual fun createDriver(): SqlDriver = NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||||
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,5 @@ import app.cash.sqldelight.db.SqlDriver
|
|||||||
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
||||||
|
|
||||||
actual class DriverFactory {
|
actual class DriverFactory {
|
||||||
actual fun createDriver(): SqlDriver {
|
actual fun createDriver(): SqlDriver = NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||||
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user