Compare commits
46
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d58fba5c9 | ||
|
|
284c19ef89 | ||
|
|
7cfd17231a | ||
|
|
527830a5ae | ||
|
|
c4ed30f594 | ||
|
|
156c1681cf | ||
|
|
3593fbca78 | ||
|
|
430fc8e8cb | ||
|
|
4fce19bad4 | ||
|
|
49f5848e7b | ||
|
|
90452100a4 | ||
|
|
bf1196dd0f | ||
|
|
4316dc6516 | ||
|
|
9833a66a64 | ||
|
|
797bf06a9c | ||
|
|
d98b00533d | ||
|
|
bf8f7d8667 | ||
|
|
89c570f34f | ||
|
|
d6a562863a | ||
|
|
a02f06fe2e | ||
|
|
7b088d7bb4 | ||
|
|
477883ed39 | ||
|
|
748ed41096 | ||
|
|
86c50d4881 | ||
|
|
c4c92e6dd9 | ||
|
|
7f0ba193ec | ||
|
|
87ed5b0fa8 | ||
|
|
6947743ac0 | ||
|
|
07e3710d44 | ||
|
|
e68da7764f | ||
|
|
c3ff894027 | ||
|
|
f09f731d30 | ||
|
|
956c4341c7 | ||
|
|
7b68264dd7 | ||
|
|
cfcf030bf8 | ||
|
|
0e7d7a5835 | ||
|
|
0856ebb889 | ||
|
|
25bf68cf0c | ||
|
|
afc6f392c6 | ||
|
|
a0b5e2052b | ||
|
|
87d1ef2bce | ||
|
|
537a6d3a0b | ||
|
|
dbe97f564e | ||
|
|
3a3bf03114 | ||
|
|
c09a32e9ad | ||
|
|
b02a588dff |
+15
-8
@@ -3,27 +3,34 @@ type: docker
|
||||
name: test
|
||||
|
||||
steps:
|
||||
- name: AnylyseBuildTest
|
||||
- name: BuildAndTest
|
||||
image: mingc/android-build-box:latest
|
||||
commands:
|
||||
- echo "---------------------------------------------------------"
|
||||
- echo "Configure gradle..."
|
||||
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nappLoginUrl=\"URL\"\nappLoginUsername=\"LOGIN\"\nappLoginPassword=\"PASS\"\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
|
||||
- echo "---------------------------------------------------------"
|
||||
- echo "Analysing..."
|
||||
- ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN
|
||||
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
|
||||
- echo "---------------------------------------------------------"
|
||||
- echo "Building..."
|
||||
- ./gradlew build
|
||||
- ./gradlew build -x test
|
||||
- echo "---------------------------------------------------------"
|
||||
- echo "Testing..."
|
||||
- echo "---------------------------------------------------------"
|
||||
- ./gradlew test
|
||||
- ./gradlew koverMergedXmlReport
|
||||
environment:
|
||||
SONAR_HOST_URL:
|
||||
from_secret: sonarScannerHostUrl
|
||||
SONAR_LOGIN:
|
||||
from_secret: sonarScannerLogin
|
||||
- name: Analyse
|
||||
image: kytay/sonar-node-plugin
|
||||
settings:
|
||||
sonar_host:
|
||||
from_secret: sonarScannerHostUrl
|
||||
sonar_token:
|
||||
from_secret: sonarScannerLogin
|
||||
use_node_version: 16.18.1
|
||||
sonar_debug: true
|
||||
sonar_project_settings: ./sonar-project.properties
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
@@ -90,7 +97,7 @@ steps:
|
||||
commands:
|
||||
- echo "---------------------------------------------------------"
|
||||
- echo "Configure gradle..."
|
||||
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nappLoginUrl=\"URL\"\nappLoginUsername=\"LOGIN\"\nappLoginPassword=\"PASS\"\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
|
||||
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
|
||||
- echo "---------------------------------------------------------"
|
||||
- echo "Generate APK"
|
||||
- ./gradlew :androidApp:assembleGithubConfigRelease -P pushCache=false
|
||||
|
||||
@@ -46,28 +46,3 @@ Always check if the web version of your instance is working.
|
||||
I won't provide any selfoss instance url. If you want to help, but to not have one, you'll have to install one, and use it.
|
||||
|
||||
All the details to need are [here](https://selfoss.aditu.de/).
|
||||
|
||||
# Build the project
|
||||
|
||||
You can directly import this project into IntellIJ/Android Studio.
|
||||
|
||||
You'll have to:
|
||||
|
||||
- Define some parameters either in `~/.gradle/gradle.properties` or as gradle parameters (see the examples)
|
||||
|
||||
- appLoginUrl, appLoginUsername and appLoginPassword: url, username and password of a selfoss instance. **These are only used for tests. They can be empty if you don't test API calls.**
|
||||
|
||||
### Examples:
|
||||
#### Inside ~/.gradle/gradle.properties
|
||||
|
||||
```
|
||||
appLoginUrl="URL" # It can be empty.
|
||||
appLoginUsername="LOGIN" # It can be empty.
|
||||
appLoginPassword="PASS" # It can be empty.
|
||||
```
|
||||
|
||||
#### As gradle parameters
|
||||
|
||||
```
|
||||
./gradlew .... -P appLoginUrl="URL" -P appLoginUsername="LOGIN" -P appLoginPassword="PASS"
|
||||
```
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
val ignoreGitVersion: String by project
|
||||
val acraVersion = "5.9.7"
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
id("com.mikepenz.aboutlibraries.plugin")
|
||||
id("org.jetbrains.kotlinx.kover") version "0.6.1"
|
||||
}
|
||||
|
||||
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
|
||||
@@ -96,9 +98,6 @@ android {
|
||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
}
|
||||
getByName("debug") {
|
||||
buildConfigField("String", "LOGIN_URL", properties["appLoginUrl"] as String)
|
||||
buildConfigField("String", "LOGIN_PASSWORD", properties["appLoginPassword"] as String)
|
||||
buildConfigField("String", "LOGIN_USERNAME", properties["appLoginUsername"] as String)
|
||||
}
|
||||
}
|
||||
flavorDimensions.add("build")
|
||||
@@ -150,9 +149,6 @@ dependencies {
|
||||
kapt("com.github.bumptech.glide:compiler:4.11.0")
|
||||
implementation("com.github.bumptech.glide:okhttp3-integration:4.1.1")
|
||||
|
||||
// Drawer
|
||||
implementation("com.mikepenz:materialdrawer:8.4.5")
|
||||
|
||||
// Themes
|
||||
implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
|
||||
|
||||
@@ -189,6 +185,12 @@ dependencies {
|
||||
testImplementation("io.mockk:mockk:1.12.0")
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
|
||||
|
||||
implementation("ch.acra:acra-http:$acraVersion")
|
||||
implementation("ch.acra:acra-toast:$acraVersion")
|
||||
|
||||
// Matomo
|
||||
implementation("com.github.matomo-org:matomo-sdk-android:4.1.4")
|
||||
}
|
||||
|
||||
tasks.withType<Test> {
|
||||
|
||||
@@ -79,8 +79,5 @@
|
||||
android:value="true" />
|
||||
|
||||
<meta-data android:name="android.max_aspect" android:value="2.1" />
|
||||
<meta-data
|
||||
android:name="preloaded_fonts"
|
||||
android:resource="@array/preloaded_fonts" />
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,9 @@
|
||||
package bou.amine.apps.readerforselfossv2.android
|
||||
|
||||
import org.acra.ACRA
|
||||
import org.acra.ktx.sendSilentlyWithAcra
|
||||
|
||||
fun Throwable.sendSilentlyWithAcraWithName(name: String) {
|
||||
ACRA.errorReporter.putCustomData("error_source", name)
|
||||
this.sendSilentlyWithAcra()
|
||||
}
|
||||
+40
-297
@@ -1,23 +1,17 @@
|
||||
package bou.amine.apps.readerforselfossv2.android
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.*
|
||||
import androidx.work.Constraints
|
||||
@@ -29,46 +23,31 @@ import bou.amine.apps.readerforselfossv2.android.adapters.ItemListAdapter
|
||||
import bou.amine.apps.readerforselfossv2.android.adapters.ItemsAdapter
|
||||
import bou.amine.apps.readerforselfossv2.android.background.LoadingWorker
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityHomeBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.fragments.FilterSheetFragment
|
||||
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.*
|
||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||
import com.ashokvarma.bottomnavigation.BottomNavigationBar
|
||||
import com.ashokvarma.bottomnavigation.BottomNavigationItem
|
||||
import com.ashokvarma.bottomnavigation.TextBadgeItem
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||
import com.mikepenz.materialdrawer.holder.BadgeStyle
|
||||
import com.mikepenz.materialdrawer.holder.ColorHolder
|
||||
import com.mikepenz.materialdrawer.holder.StringHolder
|
||||
import com.mikepenz.materialdrawer.model.DividerDrawerItem
|
||||
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem
|
||||
import com.mikepenz.materialdrawer.model.SecondaryDrawerItem
|
||||
import com.mikepenz.materialdrawer.model.interfaces.*
|
||||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.util.addStickyFooterItem
|
||||
import com.mikepenz.materialdrawer.util.updateBadge
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.closestDI
|
||||
import org.kodein.di.instance
|
||||
import org.matomo.sdk.Tracker
|
||||
import org.matomo.sdk.extra.TrackHelper
|
||||
import java.security.MessageDigest
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAware {
|
||||
|
||||
private val DRAWER_ID_TAGS = 100101L
|
||||
private val DRAWER_ID_HIDDEN_TAGS = 101100L
|
||||
private val DRAWER_ID_SOURCES = 100110L
|
||||
private val DRAWER_ID_FILTERS = 100111L
|
||||
|
||||
private var items: ArrayList<SelfossModel.Item> = ArrayList()
|
||||
|
||||
private var elementsShown: ItemType = ItemType.UNREAD
|
||||
@@ -86,8 +65,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
private var fromTabShortcut: Boolean = false
|
||||
|
||||
private lateinit var tagsBadge: Map<Long, Int>
|
||||
|
||||
private val settingsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
appSettingsService.refreshUserSettings()
|
||||
}
|
||||
@@ -95,12 +72,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
override val di by closestDI()
|
||||
private val repository : Repository by instance()
|
||||
private val appSettingsService : AppSettingsService by instance()
|
||||
private val tracker : Tracker by instance()
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityHomeBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
|
||||
TrackHelper.track().screen("/home").with(tracker)
|
||||
|
||||
fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1
|
||||
repository.offlineOverride = intent.getBooleanExtra("startOffline", false)
|
||||
|
||||
@@ -111,14 +92,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
setContentView(view)
|
||||
|
||||
setSupportActionBar(binding.toolBar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setHomeButtonEnabled(true)
|
||||
val mDrawerToggle = ActionBarDrawerToggle(this, binding.drawerContainer, binding.toolBar, R.string.material_drawer_open, R.string.material_drawer_close)
|
||||
binding.drawerContainer.addDrawerListener(mDrawerToggle)
|
||||
mDrawerToggle.syncState()
|
||||
|
||||
handleBottomBar()
|
||||
initDrawer()
|
||||
|
||||
handleSwipeRefreshLayout()
|
||||
|
||||
@@ -139,7 +114,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
repository.offlineOverride = false
|
||||
lastFetchDone = false
|
||||
handleDrawerItems()
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
getElementsAccordingToTab()
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
@@ -179,16 +153,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
adapter.handleItemAtIndex(position)
|
||||
|
||||
val tagHashes = i.tags.map { it.longHash() }
|
||||
tagsBadge = tagsBadge.map {
|
||||
if (tagHashes.contains(it.key)) {
|
||||
(it.key to (it.value - 1))
|
||||
} else {
|
||||
(it.key to it.value)
|
||||
}
|
||||
}.toMap()
|
||||
reloadTagsBadges()
|
||||
|
||||
// Just load everythin
|
||||
if (items.size <= 0) {
|
||||
getElementsAccordingToTab()
|
||||
@@ -285,8 +249,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
handleDrawerItems()
|
||||
|
||||
reloadLayoutManager()
|
||||
|
||||
if (appSettingsService.isInfiniteLoadingEnabled()) {
|
||||
@@ -297,6 +259,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
handleBottomBarActions()
|
||||
|
||||
handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
|
||||
|
||||
handleRecurringTask()
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
@@ -306,245 +270,22 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
getElementsAccordingToTab()
|
||||
}
|
||||
|
||||
private fun initDrawer() {
|
||||
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
|
||||
override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) {
|
||||
Glide.with(this@HomeActivity)
|
||||
.asBitmap()
|
||||
.load(uri)
|
||||
.apply(RequestOptions()
|
||||
.placeholder(R.mipmap.ic_launcher)
|
||||
.fallback(R.mipmap.ic_launcher)
|
||||
.fitCenter())
|
||||
.into(imageView)
|
||||
|
||||
private fun handleGDPRDialog(GDPRShown: Boolean) {
|
||||
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
|
||||
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
|
||||
if (!GDPRShown) {
|
||||
val alertDialog = AlertDialog.Builder(this).create()
|
||||
alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
|
||||
alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
|
||||
alertDialog.setButton(
|
||||
AlertDialog.BUTTON_NEUTRAL,
|
||||
"OK"
|
||||
) { dialog, _ ->
|
||||
appSettingsService.settings.putBoolean("GDPR_shown", true)
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
override fun cancel(imageView: ImageView) {
|
||||
Glide.with(this@HomeActivity).clear(imageView)
|
||||
}
|
||||
})
|
||||
|
||||
val drawerListener = object : DrawerLayout.DrawerListener {
|
||||
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
|
||||
// We do nothing
|
||||
}
|
||||
|
||||
override fun onDrawerOpened(drawerView: View) {
|
||||
binding.bottomBar.hide()
|
||||
}
|
||||
|
||||
override fun onDrawerClosed(drawerView: View) {
|
||||
binding.bottomBar.show()
|
||||
}
|
||||
|
||||
override fun onDrawerStateChanged(newState: Int) {
|
||||
// We do nothing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
binding.drawerContainer.addDrawerListener(drawerListener)
|
||||
|
||||
// Sticky items
|
||||
addStickyPrimaryItem(R.string.drawer_report_bug, R.drawable.ic_bug_report_black_24dp) { _, _, _ ->
|
||||
val browserIntent =
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.trackerUrl))
|
||||
startActivity(browserIntent)
|
||||
false
|
||||
}
|
||||
addStickyPrimaryItem(R.string.title_activity_settings, R.drawable.ic_settings_black_24dp) { _, _, _ ->
|
||||
settingsLauncher.launch(Intent(this, SettingsActivity::class.java))
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun addStickyPrimaryItem(name: Int, icon: Int, clickListener: ((v: View?, item: IDrawerItem<*>, position: Int) -> Boolean)?) {
|
||||
binding.mainDrawer.addStickyFooterItem(
|
||||
PrimaryDrawerItem().apply {
|
||||
nameRes = name
|
||||
iconRes = icon
|
||||
isIconTinted = true
|
||||
onDrawerItemClickListener = clickListener
|
||||
})
|
||||
}
|
||||
|
||||
private fun handleDrawerItems() {
|
||||
tagsBadge = emptyMap()
|
||||
binding.mainDrawer.itemAdapter.add(
|
||||
PrimaryDrawerItem().apply {
|
||||
nameRes = R.string.drawer_loading
|
||||
isSelectable = false
|
||||
}
|
||||
)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val tags = repository.getTags()
|
||||
val sources = repository.getSources()
|
||||
runOnUiThread {
|
||||
handleDrawerData(tags, sources)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDrawerData(tags: List<SelfossModel.Tag>, sources: List<SelfossModel.Source>) {
|
||||
binding.mainDrawer.itemAdapter.clear()
|
||||
|
||||
// Filters title with clear action
|
||||
secondaryItem(withDivider = false, R.string.drawer_item_filters, DRAWER_ID_FILTERS, R.string.drawer_action_clear) { _,_,_ ->
|
||||
repository.sourceFilter = null
|
||||
repository.tagFilter = null
|
||||
binding.mainDrawer.setSelectionAtPosition(-1)
|
||||
getElementsAccordingToTab()
|
||||
fetchOnEmptyList()
|
||||
false
|
||||
}
|
||||
|
||||
// Hidden tags
|
||||
if (tags.isNotEmpty() && appSettingsService.getHiddenTags().isNotEmpty()) {
|
||||
secondaryItem(
|
||||
withDivider = true,
|
||||
R.string.drawer_item_hidden_tags,
|
||||
DRAWER_ID_HIDDEN_TAGS
|
||||
)
|
||||
handleHiddenTags(tags)
|
||||
}
|
||||
|
||||
// Tags
|
||||
secondaryItem(withDivider = true, R.string.drawer_item_tags, DRAWER_ID_TAGS)
|
||||
if (tags.isEmpty()) {
|
||||
binding.mainDrawer.itemAdapter.add(
|
||||
SecondaryDrawerItem()
|
||||
.apply { nameRes = R.string.drawer_error_loading_tags; isSelectable = false }
|
||||
)
|
||||
} else {
|
||||
handleTags(tags)
|
||||
}
|
||||
|
||||
// Sources
|
||||
secondaryItem(withDivider = true, R.string.drawer_item_sources, DRAWER_ID_SOURCES, R.string.drawer_action_edit) { v, _, _ ->
|
||||
startActivity(Intent(v!!.context, SourcesActivity::class.java))
|
||||
false
|
||||
}
|
||||
if (sources.isEmpty()) {
|
||||
binding.mainDrawer.itemAdapter.add(
|
||||
SecondaryDrawerItem().apply {
|
||||
nameRes = R.string.drawer_error_loading_sources
|
||||
isSelectable = false
|
||||
}
|
||||
)
|
||||
} else {
|
||||
handleSources(sources)
|
||||
}
|
||||
|
||||
// About action
|
||||
binding.mainDrawer.itemAdapter.add(
|
||||
DividerDrawerItem(),
|
||||
PrimaryDrawerItem().apply {
|
||||
nameRes = R.string.action_about
|
||||
isSelectable = false
|
||||
iconRes = R.drawable.ic_info_outline_white_24dp
|
||||
isIconTinted = true
|
||||
onDrawerItemClickListener = { _,_,_ ->
|
||||
LibsBuilder()
|
||||
.withAboutIconShown(true)
|
||||
.withAboutVersionShown(true)
|
||||
.start(this@HomeActivity)
|
||||
false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun secondaryItem(withDivider: Boolean, name: Int, id: Long, badgeId: Int? = null, clickListener: ((v: View?, item: IDrawerItem<*>, position: Int) -> Boolean)? = null) {
|
||||
if (withDivider) {
|
||||
binding.mainDrawer.itemAdapter.add(DividerDrawerItem())
|
||||
}
|
||||
|
||||
binding.mainDrawer.itemAdapter.add(
|
||||
SecondaryDrawerItem().apply {
|
||||
nameRes = name
|
||||
identifier = id
|
||||
isSelectable = false
|
||||
if (badgeId != null) {
|
||||
badgeRes = badgeId
|
||||
}
|
||||
onDrawerItemClickListener = clickListener
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDrawerItem(it: SelfossModel.Tag) {
|
||||
val gd = GradientDrawable()
|
||||
val gdColor = try {
|
||||
Color.parseColor(it.color)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
resources.getColor(R.color.colorPrimary)
|
||||
}
|
||||
gd.setColor(gdColor)
|
||||
gd.shape = GradientDrawable.RECTANGLE
|
||||
gd.setSize(30, 30)
|
||||
gd.cornerRadius = 30F
|
||||
|
||||
val drawerItem = PrimaryDrawerItem()
|
||||
.apply {
|
||||
nameText = it.tag.getHtmlDecoded()
|
||||
identifier = it.tag.longHash()
|
||||
iconDrawable = gd
|
||||
badgeStyle = BadgeStyle().apply {
|
||||
textColor = ColorHolder.fromColor(Color.WHITE)
|
||||
color = ColorHolder.fromColor(resources.getColor(R.color.colorAccent))
|
||||
}
|
||||
onDrawerItemClickListener = { _, _, _ ->
|
||||
repository.tagFilter = it
|
||||
repository.sourceFilter = null
|
||||
getElementsAccordingToTab()
|
||||
fetchOnEmptyList()
|
||||
false
|
||||
}
|
||||
}
|
||||
if (it.unread > 0) {
|
||||
drawerItem.badgeText = it.unread.toString()
|
||||
}
|
||||
|
||||
binding.mainDrawer.itemAdapter.add(drawerItem)
|
||||
}
|
||||
|
||||
private fun handleTags(tags: List<SelfossModel.Tag>) {
|
||||
val filteredTags = tags
|
||||
.filterNot { appSettingsService.getHiddenTags().contains(it.tag) }
|
||||
.sortedBy { it.tag }
|
||||
createTagItems(filteredTags)
|
||||
}
|
||||
|
||||
private fun handleHiddenTags(tags: List<SelfossModel.Tag>) {
|
||||
val filteredHiddenTags: List<SelfossModel.Tag> =
|
||||
tags.filter { appSettingsService.getHiddenTags().contains(it.tag) }
|
||||
createTagItems(filteredHiddenTags)
|
||||
}
|
||||
|
||||
private fun createTagItems(tags: List<SelfossModel.Tag>) {
|
||||
tagsBadge = tags.associate {
|
||||
createDrawerItem(it)
|
||||
|
||||
(it.tag.longHash() to it.unread)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSources(sources: List<SelfossModel.Source>) {
|
||||
for (source in sources) {
|
||||
val item = PrimaryDrawerItem().apply {
|
||||
nameText = source.title.getHtmlDecoded()
|
||||
identifier = source.id.toLong()
|
||||
iconUrl = source.getIcon(repository.baseUrl)
|
||||
onDrawerItemClickListener = { _,_,_ ->
|
||||
repository.sourceFilter = source
|
||||
repository.tagFilter = null
|
||||
getElementsAccordingToTab()
|
||||
fetchOnEmptyList()
|
||||
false
|
||||
}
|
||||
}
|
||||
binding.mainDrawer.itemAdapter.add(item)
|
||||
alertDialog.show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -629,7 +370,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
})
|
||||
}
|
||||
|
||||
private fun fetchOnEmptyList() {
|
||||
fun fetchOnEmptyList() {
|
||||
binding.recyclerView.doOnNextLayout {
|
||||
// TODO: do if last element (or is empty ?)
|
||||
getElementsAccordingToTab(true)
|
||||
@@ -670,7 +411,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
binding.emptyText.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun getElementsAccordingToTab(
|
||||
fun getElementsAccordingToTab(
|
||||
appendResults: Boolean = false
|
||||
) {
|
||||
offset = if (appendResults && items.size > 0) {
|
||||
@@ -751,13 +492,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadTagsBadges() {
|
||||
tagsBadge.forEach {
|
||||
binding.mainDrawer.updateBadge(it.key, StringHolder(it.value.toString()))
|
||||
}
|
||||
binding.mainDrawer.resetDrawerContent()
|
||||
}
|
||||
|
||||
private fun calculateNoOfColumns(): Int {
|
||||
val displayMetrics = resources.displayMetrics
|
||||
val dpWidth = displayMetrics.widthPixels / displayMetrics.density
|
||||
@@ -803,6 +537,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_filter -> {
|
||||
val filterSheetFragment = FilterSheetFragment()
|
||||
filterSheetFragment.show(supportFragmentManager, FilterSheetFragment.TAG)
|
||||
return true
|
||||
}
|
||||
R.id.refresh -> {
|
||||
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
|
||||
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
|
||||
@@ -842,8 +581,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
).show()
|
||||
tabNewBadge.removeBadge()
|
||||
|
||||
handleDrawerItems()
|
||||
|
||||
getElementsAccordingToTab()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
@@ -860,12 +597,18 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
return true
|
||||
}
|
||||
R.id.action_disconnect -> {
|
||||
appSettingsService.clearAll()
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
repository.logout()
|
||||
}
|
||||
val intent = Intent(this, LoginActivity::class.java)
|
||||
this.startActivity(intent)
|
||||
this@HomeActivity.finish()
|
||||
return true
|
||||
}
|
||||
R.id.action_settings -> {
|
||||
settingsLauncher.launch(Intent(this, SettingsActivity::class.java))
|
||||
return true
|
||||
}
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
+29
-1
@@ -2,6 +2,7 @@ package bou.amine.apps.readerforselfossv2.android
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
@@ -24,6 +25,12 @@ import kotlinx.coroutines.launch
|
||||
import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.closestDI
|
||||
import org.kodein.di.instance
|
||||
import org.matomo.sdk.Tracker
|
||||
import org.matomo.sdk.extra.DimensionQueue
|
||||
import org.matomo.sdk.extra.DownloadTracker
|
||||
import org.matomo.sdk.extra.TrackHelper
|
||||
import java.security.MessageDigest
|
||||
|
||||
|
||||
class LoginActivity : AppCompatActivity(), DIAware {
|
||||
|
||||
@@ -35,10 +42,17 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
||||
override val di by closestDI()
|
||||
private val repository : Repository by instance()
|
||||
private val appSettingsService : AppSettingsService by instance()
|
||||
private val tracker : Tracker by instance()
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
|
||||
TrackHelper.track().download().identifier(DownloadTracker.Extra.ApkChecksum(applicationContext))
|
||||
.with(tracker)
|
||||
TrackHelper.track().screen("/login").with(tracker)
|
||||
|
||||
handleTheme()
|
||||
|
||||
binding = ActivityLoginBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
@@ -56,6 +70,11 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
||||
handleActions()
|
||||
}
|
||||
|
||||
@SuppressLint("WrongConstant") // Constant is fetched from the settings
|
||||
private fun handleTheme() {
|
||||
AppCompatDelegate.setDefaultNightMode(appSettingsService.getCurrentTheme())
|
||||
}
|
||||
|
||||
private fun handleActions() {
|
||||
|
||||
binding.passwordView.setOnEditorActionListener(
|
||||
@@ -95,6 +114,15 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
||||
private fun goToMain() {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
repository.updateApiVersion()
|
||||
|
||||
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
|
||||
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
|
||||
tracker.userId = String(messageDigest.digest())
|
||||
|
||||
val mDimensionQueue = DimensionQueue(tracker)
|
||||
mDimensionQueue.add(1, appSettingsService.getApiVersion().toString())
|
||||
|
||||
tracker.isOptOut = !appSettingsService.isAnalyticsEnabled()
|
||||
}
|
||||
val intent = Intent(this, HomeActivity::class.java)
|
||||
startActivity(intent)
|
||||
|
||||
@@ -3,21 +3,16 @@ package bou.amine.apps.readerforselfossv2.android
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.appcompat.app.AppCompatDelegate.*
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
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.network.isNetworkAccessible
|
||||
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
|
||||
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
|
||||
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
||||
@@ -26,15 +21,23 @@ import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.github.ln_12.library.ConnectivityStatus
|
||||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
||||
import io.github.aakira.napier.DebugAntilog
|
||||
import io.github.aakira.napier.Napier
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.acra.ACRA
|
||||
import org.acra.ReportField
|
||||
import org.acra.config.httpSender
|
||||
import org.acra.config.toast
|
||||
import org.acra.data.StringFormat
|
||||
import org.acra.ktx.initAcra
|
||||
import org.acra.sender.HttpSender
|
||||
import org.kodein.di.*
|
||||
import org.matomo.sdk.Matomo
|
||||
import org.matomo.sdk.Tracker
|
||||
import org.matomo.sdk.TrackerBuilder
|
||||
|
||||
class MyApp : MultiDexApplication(), DIAware {
|
||||
|
||||
@@ -45,13 +48,14 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
bind<Repository>() with singleton { Repository(instance(), instance(), isConnectionAvailable, instance()) }
|
||||
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
|
||||
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
|
||||
bind<Tracker>() with singleton { TrackerBuilder.createDefault("https://matomo.amine-louveau.fr/matomo.php", if (BuildConfig.DEBUG) 4 else 5).build(
|
||||
Matomo.getInstance(applicationContext)) }
|
||||
}
|
||||
|
||||
private val repository: Repository by instance()
|
||||
private val viewModel: AppViewModel by instance()
|
||||
private val connectivityStatus: ConnectivityStatus by instance()
|
||||
private val driverFactory: DriverFactory by instance()
|
||||
private val appSettingsService : AppSettingsService by instance()
|
||||
|
||||
// TODO: handle with the "previous" way
|
||||
private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
||||
@@ -60,28 +64,55 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
super.onCreate()
|
||||
Napier.base(DebugAntilog())
|
||||
|
||||
initDrawerImageLoader()
|
||||
if (!ACRA.isACRASenderServiceProcess()) {
|
||||
tryToHandleBug()
|
||||
|
||||
tryToHandleBug()
|
||||
handleNotificationChannels()
|
||||
|
||||
handleNotificationChannels()
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository))
|
||||
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository))
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
viewModel.networkAvailableProvider.collect { networkAvailable ->
|
||||
val toastMessage = if (networkAvailable) {
|
||||
repository.handleDBActions()
|
||||
R.string.network_connectivity_retrieved
|
||||
} else {
|
||||
R.string.network_connectivity_lost
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
viewModel.networkAvailableProvider.collect { networkAvailable ->
|
||||
val toastMessage = if (networkAvailable) {
|
||||
repository.handleDBActions()
|
||||
R.string.network_connectivity_retrieved
|
||||
} else {
|
||||
R.string.network_connectivity_lost
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
toastMessage,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
toastMessage,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
super.attachBaseContext(base)
|
||||
|
||||
initAcra {
|
||||
reportFormat = StringFormat.JSON
|
||||
reportContent = listOf(
|
||||
ReportField.REPORT_ID, ReportField.INSTALLATION_ID,
|
||||
ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME,
|
||||
ReportField.BUILD, ReportField.ANDROID_VERSION, ReportField.BRAND, ReportField.PHONE_MODEL,
|
||||
ReportField.AVAILABLE_MEM_SIZE, ReportField.TOTAL_MEM_SIZE,
|
||||
ReportField.STACK_TRACE, ReportField.APPLICATION_LOG, ReportField.LOGCAT,
|
||||
ReportField.INITIAL_CONFIGURATION, ReportField.CRASH_CONFIGURATION, ReportField.IS_SILENT,
|
||||
ReportField.USER_APP_START_DATE, ReportField.USER_COMMENT, ReportField.USER_CRASH_DATE, ReportField.USER_EMAIL, ReportField.CUSTOM_DATA)
|
||||
toast {
|
||||
//required
|
||||
text = getString(R.string.crash_toast_text)
|
||||
length = Toast.LENGTH_SHORT
|
||||
}
|
||||
httpSender {
|
||||
uri = "https://bugs.amine-louveau.fr/report" /*best guess, you may need to adjust this*/
|
||||
basicAuthLogin = "LMTlLZuazADohTCm"
|
||||
basicAuthPassword = "he6ghHp83F0PYPfh"
|
||||
httpMethod = HttpSender.Method.POST
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,52 +134,19 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
}
|
||||
}
|
||||
|
||||
private fun initDrawerImageLoader() {
|
||||
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
|
||||
override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) {
|
||||
Glide.with(imageView.context)
|
||||
.load(uri.toString())
|
||||
.apply(RequestOptions.fitCenterTransform().placeholder(placeholder))
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
override fun cancel(imageView: ImageView) {
|
||||
Glide.with(imageView.context).clear(imageView)
|
||||
}
|
||||
|
||||
override fun placeholder(ctx: Context, tag: String?): Drawable {
|
||||
return baseContext.resources.getDrawable(R.mipmap.ic_launcher)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun tryToHandleBug() {
|
||||
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler { thread, e ->
|
||||
if (e is java.lang.NoClassDefFoundError && e.stackTrace.asList().any {
|
||||
if (e is NoClassDefFoundError && e.stackTrace.asList().any {
|
||||
it.toString().contains("android.view.ViewDebug")
|
||||
}) {
|
||||
Unit
|
||||
} else {
|
||||
oldHandler.uncaughtException(thread, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
if (appSettingsService.getCurrentTheme() == MODE_NIGHT_FOLLOW_SYSTEM) {
|
||||
var mode = when (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
|
||||
Configuration.UI_MODE_NIGHT_YES -> MODE_NIGHT_YES
|
||||
else -> MODE_NIGHT_NO
|
||||
}
|
||||
setDefaultNightMode(mode)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class AppLifeCycleObserver(val connectivityStatus: ConnectivityStatus, val repository: Repository) : DefaultLifecycleObserver {
|
||||
|
||||
override fun onResume(owner: LifecycleOwner) {
|
||||
|
||||
+100
-103
@@ -25,6 +25,7 @@ import bou.amine.apps.readerforselfossv2.android.databinding.FragmentArticleBind
|
||||
import bou.amine.apps.readerforselfossv2.android.model.ParecelableItem
|
||||
import bou.amine.apps.readerforselfossv2.android.model.toModel
|
||||
import bou.amine.apps.readerforselfossv2.android.model.toParcelable
|
||||
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
|
||||
@@ -44,11 +45,14 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.acra.ktx.sendSilentlyWithAcra
|
||||
import org.acra.ktx.sendWithAcra
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.x.closestDI
|
||||
import org.kodein.di.instance
|
||||
import java.net.MalformedURLException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutionException
|
||||
@@ -66,7 +70,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
private lateinit var fab: FloatingActionButton
|
||||
private lateinit var textAlignment: String
|
||||
private var _binding: FragmentArticleBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private val binding get() = _binding
|
||||
|
||||
override val di : DI by closestDI()
|
||||
private val repository: Repository by instance()
|
||||
@@ -107,25 +111,15 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
staticBar = appSettingsService.isStaticBarEnabled()
|
||||
font = appSettingsService.getFont()
|
||||
|
||||
if (font.isNotEmpty()) {
|
||||
resId = requireContext().resources.getIdentifier(font, "font", requireContext().packageName)
|
||||
typeface = try {
|
||||
ResourcesCompat.getFont(requireContext(), resId)!!
|
||||
} catch (e: java.lang.Exception) {
|
||||
// Just to be sure
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
refreshAlignment()
|
||||
|
||||
fab = binding.fab
|
||||
fab = binding!!.fab
|
||||
|
||||
fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent))
|
||||
|
||||
fab.rippleColor = resources.getColor(R.color.colorAccentDark)
|
||||
|
||||
val floatingToolbar: FloatingToolbar = binding.floatingToolbar
|
||||
val floatingToolbar: FloatingToolbar = binding!!.floatingToolbar
|
||||
floatingToolbar.attachFab(fab)
|
||||
|
||||
floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
|
||||
@@ -173,35 +167,35 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
floatingToolbar.show()
|
||||
}
|
||||
|
||||
binding.source.text = contentSource
|
||||
binding!!.source.text = contentSource
|
||||
if (typeface != null) {
|
||||
binding.source.typeface = typeface
|
||||
binding!!.source.typeface = typeface
|
||||
}
|
||||
|
||||
if (contentText.isEmptyOrNullOrNullString()) {
|
||||
getContentFromMercury()
|
||||
} else {
|
||||
binding.titleView.text = contentTitle
|
||||
binding!!.titleView.text = contentTitle
|
||||
if (typeface != null) {
|
||||
binding.titleView.typeface = typeface
|
||||
binding!!.titleView.typeface = typeface
|
||||
}
|
||||
|
||||
htmlToWebview()
|
||||
|
||||
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
|
||||
binding.imageView.visibility = View.VISIBLE
|
||||
binding!!.imageView.visibility = View.VISIBLE
|
||||
Glide
|
||||
.with(requireContext())
|
||||
.asBitmap()
|
||||
.load(contentImage)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(binding.imageView)
|
||||
.into(binding!!.imageView)
|
||||
} else {
|
||||
binding.imageView.visibility = View.GONE
|
||||
binding!!.imageView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
binding.nestedScrollView.setOnScrollChangeListener(
|
||||
binding!!.nestedScrollView.setOnScrollChangeListener(
|
||||
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||
if (scrollY > oldScrollY) {
|
||||
floatingToolbar.hide()
|
||||
@@ -217,6 +211,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
)
|
||||
|
||||
} catch (e: InflateException) {
|
||||
e.sendSilentlyWithAcraWithName("webview not available")
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
||||
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
||||
@@ -229,7 +224,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
.show()
|
||||
}
|
||||
|
||||
return binding.root
|
||||
return binding!!.root
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@@ -247,77 +242,72 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
|
||||
private fun getContentFromMercury() {
|
||||
if (repository.isNetworkAvailable()) {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
binding!!.progressBar.visibility = View.VISIBLE
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val response = mercuryApi.query(url)
|
||||
if (response.success) {
|
||||
try {
|
||||
if (response.data != null && response.data!!.content != null && !response.data!!.content.isNullOrEmpty()) {
|
||||
try {
|
||||
val response = mercuryApi.query(url)
|
||||
if (response.success && response.data != null && !response.data?.content.isNullOrEmpty()) {
|
||||
binding!!.titleView.text = response.data!!.title.orEmpty()
|
||||
try {
|
||||
if (typeface != null) {
|
||||
binding!!.titleView.typeface = typeface
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > typeface")
|
||||
}
|
||||
|
||||
try {
|
||||
// Note: Mercury may return relative urls... If it does the url val will not be changed.
|
||||
URL(response.data!!.url)
|
||||
url = response.data!!.url
|
||||
} catch (e: MalformedURLException) {
|
||||
// Mercury returned a relative url
|
||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > malformedurlexception")
|
||||
}
|
||||
|
||||
try {
|
||||
contentText = response.data!!.content.orEmpty()
|
||||
htmlToWebview()
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > contenttext or html")
|
||||
}
|
||||
|
||||
if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) {
|
||||
try {
|
||||
binding.titleView.text = response.data!!.title
|
||||
if (typeface != null) {
|
||||
binding.titleView.typeface = typeface
|
||||
}
|
||||
binding!!.imageView.visibility = View.VISIBLE
|
||||
try {
|
||||
// Note: Mercury may return relative urls... If it does the url val will not be changed.
|
||||
URL(response.data!!.url)
|
||||
url = response.data!!.url
|
||||
} catch (e: MalformedURLException) {
|
||||
// Mercury returned a relative url. We do nothing.
|
||||
Glide
|
||||
.with(requireContext())
|
||||
.asBitmap()
|
||||
.load(
|
||||
response.data!!.lead_image_url.orEmpty()
|
||||
)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(binding!!.imageView)
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > glide lead image")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
|
||||
try {
|
||||
contentText = response.data!!.content.orEmpty()
|
||||
htmlToWebview()
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (response.data!!.lead_image_url != null && !response.data!!.lead_image_url.isNullOrEmpty() && context != null) {
|
||||
binding.imageView.visibility = View.VISIBLE
|
||||
try {
|
||||
Glide
|
||||
.with(requireContext())
|
||||
.asBitmap()
|
||||
.load(
|
||||
response.data!!.lead_image_url.orEmpty()
|
||||
)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(binding.imageView)
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
} else {
|
||||
binding.imageView.visibility = View.GONE
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (context != null) {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
binding.nestedScrollView.scrollTo(0, 0)
|
||||
|
||||
binding.progressBar.visibility = View.GONE
|
||||
} catch (e: Exception) {
|
||||
if (context != null) {
|
||||
}
|
||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > outside glide lead image")
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
openInBrowserAfterFailing()
|
||||
} catch (e: Exception) {
|
||||
if (context != null) {
|
||||
}
|
||||
}
|
||||
binding!!.imageView.visibility = View.GONE
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (context != null) {
|
||||
|
||||
try {
|
||||
binding!!.nestedScrollView.scrollTo(0, 0)
|
||||
binding!!.progressBar.visibility = View.GONE
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > scrollview")
|
||||
}
|
||||
} else {
|
||||
openInBrowserAfterFailing()
|
||||
}
|
||||
} else {
|
||||
} catch (e: SocketTimeoutException) {
|
||||
openInBrowserAfterFailing()
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > whole thing")
|
||||
openInBrowserAfterFailing()
|
||||
}
|
||||
}
|
||||
@@ -330,8 +320,8 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
|
||||
|
||||
|
||||
binding.webcontent.settings.standardFontFamily = a.getString(0)
|
||||
binding.webcontent.visibility = View.VISIBLE
|
||||
binding!!.webcontent.settings.standardFontFamily = a.getString(0)
|
||||
binding!!.webcontent.visibility = View.VISIBLE
|
||||
|
||||
val colorOnSurface = TypedValue()
|
||||
requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
|
||||
@@ -339,14 +329,14 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
val colorSurface = TypedValue()
|
||||
requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
|
||||
|
||||
binding.webcontent.settings.useWideViewPort = true
|
||||
binding.webcontent.settings.loadWithOverviewMode = true
|
||||
binding.webcontent.settings.javaScriptEnabled = false
|
||||
binding!!.webcontent.settings.useWideViewPort = true
|
||||
binding!!.webcontent.settings.loadWithOverviewMode = true
|
||||
binding!!.webcontent.settings.javaScriptEnabled = false
|
||||
|
||||
binding.webcontent.webViewClient = object : WebViewClient() {
|
||||
binding!!.webcontent.webViewClient = object : WebViewClient() {
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean {
|
||||
if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
||||
if (binding!!.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
||||
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
}
|
||||
return true
|
||||
@@ -359,19 +349,25 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
try {
|
||||
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
|
||||
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG))
|
||||
}catch ( e : ExecutionException) {}
|
||||
} catch ( e : ExecutionException) {
|
||||
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > jpeg")
|
||||
}
|
||||
}
|
||||
else if (url.lowercase(Locale.US).contains(".png")) {
|
||||
try {
|
||||
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
|
||||
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG))
|
||||
}catch ( e : ExecutionException) {}
|
||||
} catch ( e : ExecutionException) {
|
||||
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > png")
|
||||
}
|
||||
}
|
||||
else if (url.lowercase(Locale.US).contains(".webp")) {
|
||||
try {
|
||||
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
|
||||
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP))
|
||||
}catch ( e : ExecutionException) {}
|
||||
} catch ( e : ExecutionException) {
|
||||
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > webp")
|
||||
}
|
||||
}
|
||||
|
||||
return super.shouldInterceptRequest(view, url)
|
||||
@@ -384,9 +380,9 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
}
|
||||
})
|
||||
|
||||
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)}
|
||||
binding!!.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)}
|
||||
|
||||
binding.webcontent.settings.layoutAlgorithm =
|
||||
binding!!.webcontent.settings.layoutAlgorithm =
|
||||
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
||||
|
||||
var baseUrl: String? = null
|
||||
@@ -395,6 +391,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
val itemUrl = URL(url)
|
||||
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
||||
} catch (e: MalformedURLException) {
|
||||
e.sendSilentlyWithAcraWithName("htmlToWebview > item url")
|
||||
}
|
||||
|
||||
val fontName = when (font) {
|
||||
@@ -416,7 +413,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
""
|
||||
}
|
||||
|
||||
binding.webcontent.loadDataWithBaseURL(
|
||||
binding!!.webcontent.loadDataWithBaseURL(
|
||||
baseUrl,
|
||||
"""<html>
|
||||
|<head>
|
||||
@@ -469,17 +466,17 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
}
|
||||
|
||||
fun scrollDown() {
|
||||
val height = binding.nestedScrollView.measuredHeight
|
||||
binding.nestedScrollView.smoothScrollBy(0, height/2)
|
||||
val height = binding!!.nestedScrollView.measuredHeight
|
||||
binding!!.nestedScrollView.smoothScrollBy(0, height/2)
|
||||
}
|
||||
|
||||
fun scrollUp() {
|
||||
val height = binding.nestedScrollView.measuredHeight
|
||||
binding.nestedScrollView.smoothScrollBy(0, -height/2)
|
||||
val height = binding!!.nestedScrollView.measuredHeight
|
||||
binding!!.nestedScrollView.smoothScrollBy(0, -height/2)
|
||||
}
|
||||
|
||||
private fun openInBrowserAfterFailing() {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding!!.progressBar.visibility = View.GONE
|
||||
requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
|
||||
}
|
||||
|
||||
@@ -498,10 +495,10 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
}
|
||||
|
||||
fun performClick(): Boolean {
|
||||
if (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
|
||||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
||||
if (binding!!.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
|
||||
binding!!.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
||||
|
||||
val position : Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)
|
||||
val position : Int = allImages.indexOf(binding!!.webcontent.hitTestResult.extra)
|
||||
|
||||
val intent = Intent(activity, ImageActivity::class.java)
|
||||
intent.putExtra("allImages", allImages)
|
||||
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import bou.amine.apps.readerforselfossv2.android.HomeActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.engine.GlideException
|
||||
import com.bumptech.glide.request.RequestListener
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.chip.Chip
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.x.closestDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
|
||||
class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
||||
|
||||
override val di: DI by closestDI()
|
||||
private val repository: Repository by instance()
|
||||
private val appSettingsService: AppSettingsService by instance()
|
||||
|
||||
private var selectedChip: Chip? = null
|
||||
|
||||
@SuppressLint("ResourceAsColor")
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val binding =
|
||||
bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding.inflate(
|
||||
inflater,
|
||||
container,
|
||||
false
|
||||
)
|
||||
|
||||
val tagGroup = binding.tagsGroup
|
||||
val sourceGroup = binding.sourcesGroup
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val tags = repository.getTags()
|
||||
|
||||
tags.forEach { tag ->
|
||||
val c = chipForTag(tag)
|
||||
tagGroup.addView(c)
|
||||
}
|
||||
|
||||
repository.getSources().forEach { source ->
|
||||
val c = Chip(requireContext())
|
||||
|
||||
Glide.with(requireContext())
|
||||
.load(source.getIcon(repository.baseUrl))
|
||||
.listener(object : RequestListener<Drawable?> {
|
||||
override fun onLoadFailed(
|
||||
e: GlideException?,
|
||||
model: Any?,
|
||||
target: Target<Drawable?>?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onResourceReady(
|
||||
resource: Drawable?,
|
||||
model: Any?,
|
||||
target: Target<Drawable?>?,
|
||||
dataSource: DataSource?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
c.chipIcon = resource
|
||||
return false
|
||||
}
|
||||
}).preload()
|
||||
|
||||
c.text = source.title.getHtmlDecoded()
|
||||
|
||||
c.setOnCloseIconClickListener {
|
||||
(it as Chip).isCloseIconVisible = false
|
||||
selectedChip = null
|
||||
repository.setSourceFilter(null)
|
||||
}
|
||||
|
||||
c.setOnClickListener {
|
||||
if (selectedChip != null) {
|
||||
selectedChip!!.isCloseIconVisible = false
|
||||
}
|
||||
(it as Chip).isCloseIconVisible = true
|
||||
selectedChip = it
|
||||
repository.setSourceFilter(source)
|
||||
|
||||
repository.setTagFilter(null)
|
||||
}
|
||||
|
||||
|
||||
if (repository.sourceFilter.value?.equals(source) == true) {
|
||||
c.isCloseIconVisible = true
|
||||
selectedChip = c
|
||||
}
|
||||
|
||||
sourceGroup.addView(c)
|
||||
}
|
||||
|
||||
binding.progressBar2.visibility = GONE
|
||||
binding.filterView.visibility = VISIBLE
|
||||
}
|
||||
|
||||
binding.floatingActionButton2.setOnClickListener {
|
||||
(activity as HomeActivity).getElementsAccordingToTab()
|
||||
(activity as HomeActivity).fetchOnEmptyList()
|
||||
dismiss()
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private fun chipForTag(tag: SelfossModel.Tag): Chip {
|
||||
val c = Chip(requireContext())
|
||||
c.text = tag.tag
|
||||
|
||||
val gd = GradientDrawable()
|
||||
val gdColor = try {
|
||||
Color.parseColor(tag.color)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.sendSilentlyWithAcraWithName("color issue " + tag.color)
|
||||
resources.getColor(R.color.colorPrimary)
|
||||
}
|
||||
gd.setColor(gdColor)
|
||||
gd.shape = GradientDrawable.RECTANGLE
|
||||
gd.setSize(30, 30)
|
||||
gd.cornerRadius = 30F
|
||||
c.chipIcon = gd
|
||||
|
||||
c.setOnCloseIconClickListener {
|
||||
(it as Chip).isCloseIconVisible = false
|
||||
selectedChip = null
|
||||
repository.setTagFilter(null)
|
||||
}
|
||||
|
||||
c.setOnClickListener {
|
||||
if (selectedChip != null) {
|
||||
selectedChip!!.isCloseIconVisible = false
|
||||
}
|
||||
(it as Chip).isCloseIconVisible = true
|
||||
selectedChip = it
|
||||
repository.setTagFilter(tag)
|
||||
|
||||
repository.setSourceFilter(null)
|
||||
}
|
||||
|
||||
if (repository.tagFilter.value?.equals(tag) == true) {
|
||||
c.isCloseIconVisible = true
|
||||
selectedChip = c
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "ModalBottomSheet"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+4
-1
@@ -2,11 +2,13 @@ package bou.amine.apps.readerforselfossv2.android.model
|
||||
|
||||
import android.content.Context
|
||||
import android.webkit.URLUtil
|
||||
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.getImages
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import org.acra.ktx.sendSilentlyWithAcra
|
||||
|
||||
fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
|
||||
val imageUrls = this.getImages()
|
||||
@@ -23,6 +25,7 @@ fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
|
||||
}
|
||||
}
|
||||
} catch (e : Error) {
|
||||
e.sendSilentlyWithAcraWithName("preloadImages")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -35,7 +38,7 @@ fun String.toTextDrawableString(): String {
|
||||
try {
|
||||
textDrawable.append(s[0])
|
||||
} catch (e: StringIndexOutOfBoundsException) {
|
||||
// We do nothing
|
||||
e.sendSilentlyWithAcraWithName("toTextDrawableString")
|
||||
}
|
||||
}
|
||||
return textDrawable.toString()
|
||||
|
||||
+33
-1
@@ -16,17 +16,31 @@ import androidx.preference.Preference
|
||||
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.sendSilentlyWithAcraWithName
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||
import org.acra.ktx.sendSilentlyWithAcra
|
||||
import org.acra.ktx.sendWithAcra
|
||||
import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.closestDI
|
||||
import org.kodein.di.instance
|
||||
import org.matomo.sdk.Tracker
|
||||
import org.matomo.sdk.extra.TrackHelper
|
||||
|
||||
private const val TITLE_TAG = "settingsActivityTitle"
|
||||
|
||||
class SettingsActivity : AppCompatActivity(),
|
||||
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
|
||||
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, DIAware {
|
||||
override val di by closestDI()
|
||||
|
||||
private val tracker : Tracker by instance()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||
|
||||
TrackHelper.track().screen("/settings").with(tracker)
|
||||
|
||||
setContentView(binding.root)
|
||||
if (savedInstanceState == null) {
|
||||
supportFragmentManager
|
||||
@@ -91,6 +105,21 @@ class SettingsActivity : AppCompatActivity(),
|
||||
class MainPreferenceFragment : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.pref_main, rootKey)
|
||||
|
||||
preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||
AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
|
||||
true
|
||||
}
|
||||
|
||||
preferenceManager.findPreference<Preference>("action_about")?.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ ->
|
||||
context?.let {
|
||||
LibsBuilder()
|
||||
.withAboutIconShown(true)
|
||||
.withAboutVersionShown(true)
|
||||
.start(it)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +136,7 @@ class SettingsActivity : AppCompatActivity(),
|
||||
val input: Int = (dest.toString() + source.toString()).toInt()
|
||||
if (input in 1..200) return@InputFilter null
|
||||
} catch (nfe: NumberFormatException) {
|
||||
nfe.sendSilentlyWithAcraWithName("GeneralPreferenceFragment")
|
||||
Toast.makeText(activity, R.string.items_number_should_be_number, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
""
|
||||
@@ -130,6 +160,7 @@ class SettingsActivity : AppCompatActivity(),
|
||||
try {
|
||||
editText.textSize = editable.toString().toInt().toFloat()
|
||||
} catch (e: NumberFormatException) {
|
||||
e.sendSilentlyWithAcraWithName("ArticleViewerPreferenceFragment > afterTextChanged")
|
||||
}
|
||||
}
|
||||
} }
|
||||
@@ -139,6 +170,7 @@ class SettingsActivity : AppCompatActivity(),
|
||||
val input = (dest.toString() + source.toString()).toInt()
|
||||
if (input > 0) return@InputFilter null
|
||||
} catch (nfe: NumberFormatException) {
|
||||
nfe.sendSilentlyWithAcraWithName("ArticleViewerPreferenceFragment > filters")
|
||||
}
|
||||
""
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.text.Spannable
|
||||
import android.text.style.ClickableSpan
|
||||
import android.util.Patterns
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomBaseViewHolder.java */
|
||||
package bou.amine.apps.readerforselfossv2.android.utils.drawer
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
|
||||
open class CustomBaseViewHolder(var view: View) : RecyclerView.ViewHolder(view) {
|
||||
var icon: ImageView = view.findViewById(R.id.material_drawer_icon)
|
||||
var name: TextView = view.findViewById(R.id.material_drawer_name)
|
||||
var description: TextView = view.findViewById(R.id.material_drawer_description)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2015 Google Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate android:fromXDelta="100%p" android:toXDelta="0"
|
||||
android:duration="@android:integer/config_mediumAnimTime"/>
|
||||
</set>
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2015 Google Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate android:fromXDelta="0" android:toXDelta="-100%p"
|
||||
android:duration="@android:integer/config_mediumAnimTime"/>
|
||||
</set>
|
||||
@@ -0,0 +1,5 @@
|
||||
<bitmap
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:dither="true"
|
||||
android:src="@drawable/checktile"
|
||||
android:tileMode="repeat"/>
|
||||
Binary file not shown.
|
After
Width:
|
Height:
|
Size:
Gitea Version: 1.28.0+dev-108-gba7c84673b |