Commit 76f686e0 authored by Christophe Henry's avatar Christophe Henry

Merge branch 'proto_nav_menu' into 'develop'

Create navigation menu

Closes #34

See merge request !53
parents 854ed3ba 9022614f
Pipeline #2115 passed with stage
in 0 seconds
......@@ -6,6 +6,7 @@
* Implements [#39](https://git.feneas.org/christophehenry/freshrss-android/issues/39): Better accessibility for the article-related actions ([!19](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/19))
* Implements [#49](https://git.feneas.org/christophehenry/freshrss-android/issues/49): Emotionnal design ([!35](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/35))
* Implements [#66](https://git.feneas.org/christophehenry/freshrss-android/issues/66): French translations (Fipaddict, [!38](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/38))
* Implements [#34](https://git.feneas.org/christophehenry/freshrss-android/issues/34): Implements feeds retrieval scheduling in settings as well as hability to disable it ([!53](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/53))
## Bug fixes
......
......@@ -18,25 +18,58 @@ pipeline {
gitLabConnection('GitlabFeneas')
}
triggers {
gitlab(triggerOnPush: true, triggerOnMergeRequest: true, branchFilterType: 'All')
gitlab(
branchFilterType: 'All',
triggerOnPush: true,
triggerOnMergeRequest: false,
triggerOpenMergeRequestOnPush: "never",
triggerOnNoteRequest: true,
noteRegex: "Jenkins please retry a build",
skipWorkInProgressMergeRequest: false,
ciSkip: false,
setBuildDescription: true,
addNoteOnMergeRequest: true,
addCiMessage: true,
addVoteOnMergeRequest: true,
acceptMergeRequestOnSuccess: false,
)
}
stages {
stage("Pre") {
steps {
updateGitlabCommitStatus name: "Pre", state: "running"
library "android-pipeline-steps"
sh "apt-get install net-tools"
}
post {
failure {
updateGitlabCommitStatus name: "Pre", state: "failed"
}
success {
updateGitlabCommitStatus name: "Pre", state: "success"
}
}
}
stage("Compile") {
steps {
updateGitlabCommitStatus name: "Compile", state: "running"
sh "./gradlew compileReleaseSources"
}
post {
failure {
updateGitlabCommitStatus name: "Compile", state: "failed"
}
success {
updateGitlabCommitStatus name: "Compile", state: "success"
}
}
}
stage("Static analysis") {
steps {
updateGitlabCommitStatus name: "Static analysis", state: "running"
sh "./gradlew lintRelease --continue"
androidLint pattern: "**/lint-results-*.xml"
publishHTML([
......@@ -49,9 +82,19 @@ pipeline {
reportTitles : ""
])
}
post {
failure {
updateGitlabCommitStatus name: "Static analysis", state: "failed"
}
success {
updateGitlabCommitStatus name: "Static analysis", state: "success"
}
}
}
stage("Unit tests") {
steps {
updateGitlabCommitStatus name: "Unit tests", state: "running"
sh "./gradlew testReleaseUnitTest --info"
junit "**/test-results/**/*.xml"
publishHTML([
......@@ -64,20 +107,49 @@ pipeline {
reportTitles : ""
])
}
post {
failure {
updateGitlabCommitStatus name: "Unit tests", state: "failed"
}
success {
updateGitlabCommitStatus name: "Unit tests", state: "success"
}
}
}
stage("Instrumented tests on min SDK image") {
steps {
updateGitlabCommitStatus name: "Instrumented tests on min SDK image", state: "running"
withAvd(hardwareProfile: "Nexus 5X", systemImage: env.MIN_SDK_IMAGE, headless: true) {
sh "./gradlew clean connectedDebugAndroidTest"
}
}
post {
failure {
updateGitlabCommitStatus name: "Instrumented tests on min SDK image", state: "failed"
}
success {
updateGitlabCommitStatus name: "Instrumented tests on min SDK image", state: "success"
}
}
}
stage("Instrumented tests on target SDK image") {
steps {
updateGitlabCommitStatus name: "Instrumented tests on target SDK image", state: "running"
withAvd(hardwareProfile: "Nexus 5X", systemImage: env.TARGET_SDK_IMAGE, headless: true) {
sh "./gradlew clean connectedDebugAndroidTest"
}
}
post {
failure {
updateGitlabCommitStatus name: "Instrumented tests on target SDK image", state: "failed"
}
success {
updateGitlabCommitStatus name: "Instrumented tests on target SDK image", state: "success"
}
}
}
// stage("Build APK") {
// steps {
......@@ -90,12 +162,6 @@ pipeline {
}
post {
failure {
updateGitlabCommitStatus name: "build", state: "failed"
}
success {
updateGitlabCommitStatus name: "build", state: "success"
}
always {
sh "chown -R jenkins ${env.WORKSPACE}"
}
......
......@@ -34,7 +34,7 @@ android {
}
lintOptions {
disable "AllowBackup", "VectorPath", "GradleDependency"
disable "AllowBackup", "VectorPath", "GradleDependency", "MissingTranslation"
}
sourceSets {
......
......@@ -10,6 +10,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.room.Room
import fr.chenry.android.freshrss.RefresherService.RefresherBinder
import fr.chenry.android.freshrss.store.FreshRSSPreferences
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase_Migrations
......@@ -21,11 +22,11 @@ import nl.komponents.kovenant.deferred
import java.util.Properties
class FreshRSSApplication : Application() {
private val refreshDelay: Long get() = 30
private val _refresherService = MutableLiveData<RefresherService>()
private val serviceConnection = RefresherServiceConnection()
val refresherService: LiveData<RefresherService> get() = _refresherService
val preferences: FreshRSSPreferences = FreshRSSPreferences(this)
val database by lazy {
val dbName = "${context.getString(R.string.app_name).toLowerCase()}.db"
......@@ -41,6 +42,8 @@ class FreshRSSApplication : Application() {
// application globally even though it's an actual a singleton...
FreshRSSApplication.applicationPromise.resolve(this)
preferences.initDefaults()
bindService(Intent(this, RefresherService::class.java), serviceConnection, Context.BIND_AUTO_CREATE)
Store.debugMode = Try {
......@@ -65,32 +68,48 @@ class FreshRSSApplication : Application() {
val context: Context get() = application.applicationContext
val notificationManager: NotificationManagerCompat
get() = NotificationManagerCompat.from(application)
val stateSharedPreferences: SharedPreferences
get() = context.getSharedPreferences("STATE", Context.MODE_PRIVATE)
val preferences: FreshRSSPreferences get() = application.preferences
fun getStringR(id: Int, vararg formatArgs: Any = arrayOf()) = if (formatArgs.isEmpty())
fun getStringR(id: Int, vararg formatArgs: Any = arrayOf()): String = if (formatArgs.isEmpty())
application.resources.getString(id) else
application.resources.getString(id, *formatArgs)
}
inner class RefresherServiceConnection : ServiceConnection {
inner class RefresherServiceConnection : ServiceConnection, SharedPreferences.OnSharedPreferenceChangeListener {
private val handler = Handler()
private val token = object {}
override fun onServiceDisconnected(name: ComponentName?) {
handler.removeCallbacksAndMessages(token)
_refresherService.value = null
}
private var refreshFrequency: Long = 30L
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
refreshFrequency = this@FreshRSSApplication.preferences.refreshFrequency
(service as RefresherBinder).service.let {
_refresherService.value = it
this@FreshRSSApplication.preferences.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
postDelayedRefesh()
}
}
override fun onServiceDisconnected(name: ComponentName?) {
handler.removeCallbacksAndMessages(token)
this@FreshRSSApplication.preferences.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
_refresherService.value = null
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
val newRefreshFrequency = this@FreshRSSApplication.preferences.refreshFrequency
if(refreshFrequency == newRefreshFrequency) return
refreshFrequency = newRefreshFrequency
handler.removeCallbacksAndMessages(token)
this.postDelayedRefesh()
}
private fun postDelayedRefesh() {
handler.postDelayed(this@FreshRSSApplication.refreshDelay * 60 * 1000, token) {
// Deactivate when refreshFrequency is 0 meaning automatic refresh is off
if(this.refreshFrequency == 0L) return
handler.postDelayed(this.refreshFrequency * 60 * 1000, token) {
this@FreshRSSApplication.refresherService.value.whenNotNull {
it.refresh(false)
postDelayedRefesh()
......
package fr.chenry.android.freshrss.activities
import android.os.Bundle
import android.view.MenuItem
import android.widget.TextView
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.Observer
import androidx.navigation.NavController
import androidx.navigation.Navigation
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection
import fr.chenry.android.freshrss.*
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.utils.whenNotNull
import kotlinx.android.synthetic.main.activity_main.*
const val SUBSCRIPTION_SECTION = "SUBSCRIPTION_SECTION"
class MainActivity : AppCompatActivity() {
class MainActivity: AppCompatActivity() {
private val navigation: NavController by lazy {
Navigation.findNavController(this, R.id.main_activity_host_fragment)
}
private val appBarConfiguration by lazy { AppBarConfiguration(navigation.graph) }
private val drawerLayout by lazy {
findViewById<DrawerLayout>(R.id.activity_main_navigation_drawer)
}
private val drawerToggle by lazy {
ActionBarDrawerToggle(this, drawerLayout, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
}
private val appBarConfiguration by lazy {AppBarConfiguration(navigation.graph)}
private val backstackCount get() = main_activity_host_fragment?.childFragmentManager?.backStackEntryCount ?: 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
restoreState()
setContentView(R.layout.activity_main)
setupActionBarWithNavController(navigation, appBarConfiguration)
FreshRSSApplication.application.refresherService.value.whenNotNull { it.refresh() }
FreshRSSApplication.application.refresherService.value.whenNotNull {it.refresh()}
drawerLayout.addDrawerListener(drawerToggle)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeButtonEnabled(true)
drawerToggle.syncState()
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
if(item?.itemId == android.R.id.home && backstackCount == 0) {
val isOpened = drawerLayout.isDrawerOpen(activity_main_navigation_view)
if(isOpened) drawerLayout.closeDrawers()
else drawerLayout.openDrawer(activity_main_navigation_view)
return true
}
return super.onOptionsItemSelected(item)
}
fun onSettingItemClick(item: MenuItem): Boolean {
drawerLayout.closeDrawers()
navigation.navigate(MainNavDirections.actionGlobalSettingsFragment())
return true
}
override fun onResume() {
restoreState()
super.onResume()
activity_main_navigation_view
.getHeaderView(0)
.findViewById<TextView>(R.id.navigation_header_container_user)
.apply {
text = Store.account.value!!.login
Store.account.observe(this@MainActivity, Observer {text = Store.account.value!!.login})
}
}
override fun onPause() {
......@@ -50,18 +92,23 @@ class MainActivity : AppCompatActivity() {
super.onRestoreInstanceState(savedInstanceState)
}
override fun onSupportNavigateUp() = navigation.navigateUp() || super.onSupportNavigateUp()
override fun onSupportNavigateUp(): Boolean {
val result = navigation.navigateUp() || super.onSupportNavigateUp()
if(backstackCount == 1) {
drawerToggle.syncState()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeButtonEnabled(true)
}
return result
}
private fun restoreState() {
Store.subscriptionsSection.value = SubscriptionSection.valueOf(
FreshRSSApplication
.stateSharedPreferences
.getString(SUBSCRIPTION_SECTION, SubscriptionSection.ALL.name)!!
)
Store.subscriptionsSection.value = FreshRSSApplication.preferences.subscriptionSection
}
private fun saveState() {
FreshRSSApplication.stateSharedPreferences.edit()
.putString(SUBSCRIPTION_SECTION, Store.subscriptionsSection.value!!.name).apply()
FreshRSSApplication.application.preferences.subscriptionSection = Store.subscriptionsSection.value!!
}
}
package fr.chenry.android.freshrss.components.settings
import android.os.Bundle
import androidx.preference.*
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.R
class SettingsFragment: PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener {
private val refreshFrequencyPreference: ListPreference by lazy {
findPreference(FreshRSSApplication.preferences.refreshFrequencyKey) as ListPreference
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.preference_screen)
refreshFrequencyPreference.apply {
this@SettingsFragment.onPreferenceChange(this, FreshRSSApplication.preferences.refreshFrequency)
onPreferenceChangeListener = this@SettingsFragment
}
}
override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
if(preference.key == refreshFrequencyPreference.key) {
refreshFrequencyPreference.findIndexOfValue(newValue.toString()).let {
val newValueStr = if(it < 0) FreshRSSApplication.getStringR(R.string.refresh_frequency_30m) else
refreshFrequencyPreference.entries[it]
refreshFrequencyPreference.title =
FreshRSSApplication.getStringR(R.string.refresh_frequency_title, newValueStr)
}
return true
}
return false
}
}
......@@ -11,7 +11,7 @@ enum class SubscriptionSection(val navigationButtonId: Int) : Parcelable {
UNREAD(R.id.subscriptions_bottom_navigation_unread);
companion object {
fun byPosition(position: Int) = SubscriptionSection.values().let {
fun byPosition(position: Int) = values().let {
if (position > it.size - 1) ALL else it[position]
}
......
package fr.chenry.android.freshrss.store
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection
import fr.chenry.android.freshrss.utils.Try
class FreshRSSPreferences(private val application: FreshRSSApplication) {
val sharedPreferences: SharedPreferences by lazy {PreferenceManager.getDefaultSharedPreferences(application)}
val refreshFrequencyKey by lazy {getKey(R.string.refresh_frequency_preference)}
val subscriptionSectionKey by lazy {getKey(R.string.subscription_section_preference)}
var refreshFrequency: Long
get() {
val pref = sharedPreferences.getString(refreshFrequencyKey, defaultRefreshFrequencyValue)!!
if(pref !in refreshFrequencyValues) return defaultRefreshFrequencyValue.toLong()
return pref.toLong()
}
set(value) {
val valueStr = value.toString()
if(valueStr !in refreshFrequencyValues) return
sharedPreferences.edit().putString(refreshFrequencyKey, valueStr).apply()
}
var subscriptionSection: SubscriptionSection
get() {
val pref = sharedPreferences.getString(subscriptionSectionKey, SubscriptionSection.ALL.name)!!
return Try{SubscriptionSection.valueOf(pref)}.getOrDefault(SubscriptionSection.ALL)
}
set(value) {
sharedPreferences.edit().putString(subscriptionSectionKey, value.name).apply()
}
private val refreshFrequencies by lazy {application.resources.getStringArray(R.array.refresh_frequencies)}
private val refreshFrequencyValues by lazy {application.resources.getStringArray(R.array.refresh_frequency_values)}
private val defaultRefreshFrequencyValue by lazy {
refreshFrequencies.indexOf(getKey(R.string.refresh_frequency_30m)).let {refreshFrequencyValues[it]}
}
fun initDefaults() {
if(sharedPreferences.getString(refreshFrequencyKey, null) == null) {
refreshFrequency = defaultRefreshFrequencyValue.toLong()
}
if(sharedPreferences.getString(subscriptionSectionKey, null) == null) {
this.subscriptionSection = SubscriptionSection.ALL
}
}
private fun getKey(id: Int): String = application.resources.getString(id)
}
package fr.chenry.android.freshrss.store
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.toLiveData
import androidx.lifecycle.*
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection
import fr.chenry.android.freshrss.store.api.Api
import fr.chenry.android.freshrss.store.database.models.Account
import fr.chenry.android.freshrss.store.database.models.Article
import fr.chenry.android.freshrss.store.database.models.ReadStatus
import fr.chenry.android.freshrss.store.database.models.*
import fr.chenry.android.freshrss.store.database.models.ReadStatus.READ
import fr.chenry.android.freshrss.store.database.models.ReadStatus.UNREAD
import fr.chenry.android.freshrss.store.database.models.Subscription
import fr.chenry.android.freshrss.store.database.models.SubscriptionCategory.Companion.fromApiItem
import fr.chenry.android.freshrss.store.database.models.VoidAccount
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.all
import fr.chenry.android.freshrss.utils.whenNotNull
import fr.chenry.android.freshrss.utils.whenNull
import nl.komponents.kovenant.*
import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.task
import nl.komponents.kovenant.then
import nl.komponents.kovenant.toSuccessVoid
import java.util.concurrent.ConcurrentHashMap
import kotlin.math.max
......@@ -29,6 +21,14 @@ object Store {
val subscriptionsSection = MutableLiveData<SubscriptionSection>().apply { this.value = SubscriptionSection.ALL }
val tags = MutableLiveData<List<String>>().apply { this.value = listOf() }
val refreshingPromise = MutableLiveData<Promise<Unit, Exception>>()
val account by lazy {
MutableLiveData<Account>().apply {
accountLiveData.value?.firstOrNull().whenNull {value = VoidAccount}.whenNotNull {value = it}
accountLiveData.observeForever {self ->
self.firstOrNull().whenNull {value = VoidAccount}.whenNotNull {value = it}
}
}
}
private var lastFetchTimestamp = 0L
private var api: Api
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/activity_main_navigation_drawer"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
......@@ -8,6 +9,7 @@
android:gravity="center_horizontal"
android:orientation="vertical"
tools:context=".activities.MainActivity">
<fragment
android:id="@+id/main_activity_host_fragment"
android:layout_width="match_parent"
......@@ -15,4 +17,12 @@
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true" />
</LinearLayout>
\ No newline at end of file
<com.google.android.material.navigation.NavigationView
android:id="@+id/activity_main_navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/navigation_drawer_header"
app:menu="@menu/activity_main_drawer"/>
</androidx.drawerlayout.widget.DrawerLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/logo_obsidian"
android:paddingBottom="@dimen/subscription_section_v_padding"
android:paddingTop="@dimen/subscription_section_v_padding"
android:paddingStart="@dimen/subscription_section_h_padding"
android:paddingEnd="@dimen/subscription_section_h_padding">
<ImageView
android:id="@+id/navigation_header_container_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/subscription_section_h_padding"
android:layout_marginStart="@dimen/subscription_section_h_padding"
android:layout_marginTop="@dimen/subscription_section_v_padding"
android:layout_marginBottom="@dimen/subscription_section_h_padding"
android:src="@mipmap/ic_launcher_round"
android:contentDescription="@string/freshrss_logo"/>
<TextView
android:id="@+id/navigation_header_container_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textColor="@color/logo_grey"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginTop="@dimen/subscription_section_v_padding"
android:layout_toEndOf="@+id/navigation_header_container_logo"/>
<TextView
android:id="@+id/navigation_header_container_user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/user"
android:textColor="@color/logo_grey"
android:textSize="16sp"
android:textStyle="bold"
android:layout_toEndOf="@+id/navigation_header_container_logo"
android:layout_marginBottom="@dimen/subscription_section_h_padding"
android:layout_below="@+id/navigation_header_container_title"/>
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!--<item-->
<!--android:title="@string/title_add_subscription"-->
<!--android:id="@+id/activity_main_drawer_add_subscription"-->
<!--android:icon="@drawable/ic_add_black_24dp"/>-->
<item android:title="@string/application">
<menu>
<item
android:title="@string/title_settings"
android:id="@+id/activity_main_drawer_settings"
android:icon="@drawable/ic_settings_black_24dp"
android:onClick="onSettingItemClick"/>
</menu>
</item>
</menu>
\ No newline at end of file
......@@ -64,4 +64,10 @@
app:exitAnim="@anim/nav_default_pop_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<fragment android:id="@+id/settingsFragment"
android:name="fr.chenry.android.freshrss.components.settings.SettingsFragment"
android:label="@string/title_settings"/>
<action android:id="@+id/action_global_settingsFragment" app:destination="@id/settingsFragment"
app:enterAnim="@anim/nav_default_pop_enter_anim" app:exitAnim="@anim/nav_default_pop_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim" app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
</navigation>
\ No newline at end of file
......@@ -53,9 +53,11 @@
<string name="notification_channel_refresh_title">Événements de raffraîchissement</string>
<string name="notification_channel_refresh_description">Événements lors de la récupération de contenus de votre serveur FreshRSS</string>
<string name="notification_channel_errors_title">Erreurs</string>
<string name="notification_channel_errors_description">FreshRSS a rencontre une erreur dont vous devriez être
<string name="notification_channel_errors_description">FreshRSS a rencontré une erreur dont vous devriez être
informé
</string>
<string name="no_internet_connection_avaible">There is no internet connection avaible for now, please retry later
</string>
<string name="no_internet_connection_avaible">Aucune connexion internet disponible, veuillez réessayer plus tard.</string>
<string name="nav_header_subtitle">Menu</string>
<string name="title_settings">Configuration</string>
<string name="application">Application</string>
</resources>
\ No newline at end of file
......@@ -13,4 +13,5 @@
<dimen name="badge_radius">@dimen/badge_width</dimen>
<dimen name="badge_margin_start">10dp</dimen>
<dimen name="badge_margin_bottom">30dp</dimen>
<dimen name="settings_item_min_height">60dp</dimen>
</resources>
\ No newline at end of file
......@@ -5,6 +5,29 @@
<item>https://</item>
<item>http://</item>
</string-array>
<string-array name="refresh_frequencies">
<item>@string/refresh_frequency_off</item>
<item>@string/refresh_frequency_30m</item>
<item>@string/refresh_frequency_1h</item>
<item>@string/refresh_frequency_5h</item>
<item>@string/refresh_frequency_12h</item>
<item>@string/refresh_frequency_1d</item>
</string-array>
<string-array name="refresh_frequency_values">
<item>0</item>
<item>30</item>
<item>60</item>
<item>300</item>
<item>720</item>
<item>1440</item>
</string-array>
<!-- Preference keys -->
<string name="refresh_frequency_preference" translatable="false">refresh_frequency_preference</string>
<string name="subscription_section_preference" translatable="false">subscription_section_preference</string>
<string name="prompt_login">Login</string>
<string name="prompt_password">Password</string>
<string name="action_sign_in">Sign in</string>
......@@ -63,8 +86,22 @@
<string name="share_article">Share article of %s</string>
<string name="this_feed">this feed</string>
<string name="instance_url_malformed">This URL is malformed</string>
<string name="no_internet_connection_avaible">There is no internet connection avaible for now, please retry later
</string>
<string name="no_internet_connection_avaible">There is no internet connection avaible for now, please retry later</string>