Commit 02386b09 authored by Christophe Henry's avatar Christophe Henry

Merge branch '21-22' into 'develop'

Solves #21 and #22

Closes #21

See merge request !8
parents fde907ce a48edbbf
......@@ -24,6 +24,8 @@
* Feed subscriptions uses a local DB ([edfd4fc5](https://git.feneas.org/christophehenry/freshrss-android/commit/edfd4fc5cde846ca6040c55b31179e107b654ddf))
* Cleanup in DataBing classes ([353f37d1](https://git.feneas.org/christophehenry/freshrss-android/commit/353f37d15c12cad108610ec3061d7f09503b290b))
* [#21](https://git.feneas.org/christophehenry/freshrss-android/issues/21): Transfrom DB instance from a singleton to an application's property ([!8](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/8))
* [#22](https://git.feneas.org/christophehenry/freshrss-android/issues/22): Refactor AuthTokenDelegates to use RxJava's Flowable ([!8](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/8))
# 1.0.1
......
......@@ -20,21 +20,26 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
<activity
android:name=".activities.StartActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".RefresherService"
android:enabled="true"
android:exported="false">
</service>
<activity android:name=".activities.TestActivity"></activity>
<activity
android:name=".activities.LoginActivity"
android:label="@string/app_name"
android:persistableMode="persistAcrossReboots">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activities.MainActivity"
......
......@@ -8,9 +8,11 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.os.postDelayed
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.Store
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase_Migrations
import fr.chenry.android.freshrss.utils.Try
import fr.chenry.android.freshrss.utils.whenNotNull
import nl.komponents.kovenant.android.startKovenant
......@@ -21,15 +23,22 @@ 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
private val serviceConnection = RefresherServiceConnection()
val database by lazy {
val dbName = "${context.getString(R.string.app_name).toLowerCase()}.db"
Room.databaseBuilder(context, FreshRSSDabatabase::class.java, dbName)
.addMigrations(*FreshRSSDabatabase_Migrations.build())
.build()
}
override fun onCreate() {
super.onCreate()
startKovenant()
// Stupid hack because Android is still not able to provide the current application application globally
// even though it is effectively a singleton...
// Stupid hack because Android is still not able to provide the current
// application globally even though it's an actual a singleton...
FreshRSSApplication.applicationPromise.resolve(this)
bindService(Intent(this, RefresherService::class.java), serviceConnection, Context.BIND_AUTO_CREATE)
......@@ -51,19 +60,13 @@ class FreshRSSApplication: Application() {
companion object {
private val applicationPromise = deferred<FreshRSSApplication, Throwable>()
val application: FreshRSSApplication
get() = applicationPromise.promise.get()
val application: FreshRSSApplication get() = applicationPromise.promise.get()
val database get() = application.database
val context: Context get() = application.applicationContext
val notificationManager: NotificationManagerCompat
get() = NotificationManagerCompat.from(FreshRSSApplication.application)
val stateSharedPreferences: SharedPreferences
get() =
context.getSharedPreferences("STATE", Context.MODE_PRIVATE)
val database get() = FreshRSSDabatabase.instance
get() = context.getSharedPreferences("STATE", Context.MODE_PRIVATE)
fun getStringR(id: Int, vararg formatArgs: Any = arrayOf()) = if(formatArgs.isEmpty())
application.resources.getString(id) else
......
......@@ -11,12 +11,12 @@ import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.addTextChangedListener
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.api.Endpoints
import fr.chenry.android.freshrss.utils.cleanUrlSlashes
import fr.chenry.android.freshrss.utils.e
import fr.chenry.android.freshrss.utils.*
import kotlinx.android.synthetic.main.activity_login.*
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
......@@ -29,12 +29,6 @@ class LoginActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if(FreshRSSDabatabase.instance.authTokensExistInDB) {
Store.init(FreshRSSDabatabase.instance.account)
startNextActivity()
return
}
setContentView(R.layout.activity_login)
// Set up the login form.
password.setOnEditorActionListener(TextView.OnEditorActionListener {_, id, _ ->
......@@ -62,12 +56,6 @@ class LoginActivity: AppCompatActivity() {
instance.requestFocus()
}
private fun startNextActivity() {
startActivity(Intent(this, MainActivity::class.java))
//startActivity(Intent(this, TestActivity::class.java))
finish()
}
private fun attemptLogin() {
resetErrors()
......@@ -98,7 +86,8 @@ class LoginActivity: AppCompatActivity() {
} else {
showProgress(true)
Store.login(instanceStr, loginStr, passwordStr) successUi {
startNextActivity()
startActivity(Intent(this, MainActivity::class.java))
finish()
} failUi {
this.e(it)
instance.error = getString(R.string.error_instance)
......
package fr.chenry.android.freshrss.activities
import android.os.Bundle
import android.os.PersistableBundle
import androidx.appcompat.app.AppCompatActivity
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.*
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.store.Store
import fr.chenry.android.freshrss.utils.*
import nl.komponents.kovenant.deferred
import nl.komponents.kovenant.resolve
import nl.komponents.kovenant.ui.*
import fr.chenry.android.freshrss.utils.whenNotNull
const val SUBSCRIPTION_SECTION = "SUBSCRIPTION_SECTION"
class MainActivity: AppCompatActivity() {
private val SUBSCRIPTION_SECTION = "SUBSCRIPTION_SECTION"
private val deferred = deferred<Unit, Exception>()
private val navigation: NavController by lazy {
Navigation.findNavController(this, R.id.main_activity_host_fragment)
}
private val appBarConfiguration by lazy {AppBarConfiguration(navigation.graph)}
init {
// TODO: Do refresh in the application when database ans Store are also handled within
val observer = object: Observer<RefresherService> {
override fun onChanged(t: RefresherService?) {
t.whenNotNull {
it.refresh()
.successUi {deferred.resolve()}
.failUi(deferred::reject)
.alwaysUi {FreshRSSApplication.application.refresherService.removeObserver(this)}
}
}
}
FreshRSSApplication.application.refresherService.observeForever(observer)
}
override fun onStart() {
super.onStart()
deferred.promise failUi {this.e(it)}
}
private val appBarConfiguration by lazy {AppBarConfiguration(navigation.graph)}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
restoreState()
setContentView(R.layout.activity_main)
setupActionBarWithNavController(navigation, appBarConfiguration)
FreshRSSApplication.application.refresherService.value.whenNotNull {
it.refresh()
}
}
override fun onResume() {
......
package fr.chenry.android.freshrss.activities
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.postDelayed
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import fr.chenry.android.freshrss.*
import fr.chenry.android.freshrss.store.database.models.VoidAccount
import fr.chenry.android.freshrss.store.viewmodels.AccountVM
import fr.chenry.android.freshrss.utils.*
import kotlinx.android.synthetic.main.activity_start.*
import nl.komponents.kovenant.ui.alwaysUi
import nl.komponents.kovenant.ui.failUi
import org.joda.time.LocalDateTime
class StartActivity: AppCompatActivity() {
private val model by lazy {ViewModelProviders.of(this).get(AccountVM::class.java)}
private val observer = object: Observer<RefresherService> {
override fun onChanged(t: RefresherService?) {
t.whenNotNull {
it.refresh()
.failUi {err -> this.e(err)}
.alwaysUi {FreshRSSApplication.application.refresherService.removeObserver(this)}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_start)
val now = LocalDateTime.now()
val userName = model.liveData.value.let {
it.whenNotNull {
FreshRSSApplication.application.refresherService.observeForever(observer)
}
when(it) {
null -> ""
VoidAccount -> ""
else -> " ${it.login}"
}
}
activity_start_welcome_text.text = when {
now.isBefore(now.withTime(11, 0, 0, 0)) ->
FreshRSSApplication.getStringR(R.string.good_morning_user, userName)
now.isBefore(now.withTime(18, 0, 0, 0)) ->
FreshRSSApplication.getStringR(R.string.hello_user, userName)
else -> FreshRSSApplication.getStringR(R.string.good_evening_user, userName)
}
}
override fun onResume() {
super.onResume()
model.liveData.value.whenNotNull {
startNextActivity(MainActivity::class.java)
}.whenNull {
startNextActivity(LoginActivity::class.java)
}
}
private fun startNextActivity(clazz: Class<out AppCompatActivity>) {
Handler().postDelayed(2000) {
startActivity(Intent(this, clazz))
finish()
}
}
}
package fr.chenry.android.freshrss.activities
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.Store
import kotlinx.android.synthetic.main.activity_test.*
import nl.komponents.kovenant.ui.successUi
class TestActivity: AppCompatActivity() {
val promise = Store.api.getUnreadItems(0)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
}
override fun onStart() {
super.onStart()
promise.successUi { test_text.text = "" }
}
}
package fr.chenry.android.freshrss.store
import androidx.lifecycle.MutableLiveData
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.FreshRSSDabatabase
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.utils.e
import nl.komponents.kovenant.*
import nl.komponents.kovenant.functional.bind
object Store {
lateinit var api: Api
private set
var debugMode = false
val totalUnreadCount = MutableLiveData<Int>()
val subscriptionsSection = MutableLiveData<SubscriptionSection>().apply {this.value = SubscriptionSection.ALL}
val tags = MutableLiveData<List<String>>().apply {this.value = listOf()}
val refreshingPromise = MutableLiveData<Promise<Unit, Exception>>()
private var lastFetchTimestamp = 0L
private var api: Api
private val accountLiveData: LiveData<List<Account>>
fun init(account: Account) {
FreshRSSDabatabase.instance.account = account
init {
val flowable = FreshRSSApplication.database.getAccount()
val account = flowable.blockingFirst().firstOrNull() ?: VoidAccount
api = Api(account)
accountLiveData = FreshRSSApplication.database.getAccount().toLiveData().apply {
observeForever {if(it.isNotEmpty()) api = Api(it.first())}
}
}
fun login(instance: String, user: String, password: String): Promise<Unit, Exception> =
Api.login(instance, user, password) then {init(it)}
Api.login(instance, user, password) then {FreshRSSApplication.database.insertAccount(it)}
fun ensureToken(): Promise<Unit, Exception> {
if(!FreshRSSDabatabase.instance.account.isWriteTokenExpired) return Promise.ofSuccess(Unit)
if(!api.account.isWriteTokenExpired) return Promise.ofSuccess(Unit)
return api.getWriteToken()
.success {FreshRSSDabatabase.instance.account.writeToken = it}
.success {api.account.writeToken = it}
.toSuccessVoid()
}
......@@ -74,7 +77,7 @@ object Store {
api.getUnreadItems(lastFetchTimestamp) bind {
val promises = it.items.map {item ->
task {
FreshRSSDabatabase.instance.upsertArticle(Article.fromContentItem(item).copy(readStatus = UNREAD))
FreshRSSApplication.database.upsertArticle(Article.fromContentItem(item).copy(readStatus = UNREAD))
}
}
lastFetchTimestamp = System.currentTimeMillis() % 1000
......
......@@ -7,7 +7,7 @@ import fr.chenry.android.freshrss.utils.*
import nl.komponents.kovenant.*
import nl.komponents.kovenant.functional.bind
class Api(private val account: Account) {
class Api(val account: Account) {
private val endpoints = Endpoints(account.serverInstance)
fun getWriteToken() = Fuel
......
......@@ -3,30 +3,25 @@ package fr.chenry.android.freshrss.store.database
import android.graphics.Bitmap
import androidx.room.*
import dev.matrix.roomigrant.GenerateRoomMigrations
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.api.models.StreamId
import fr.chenry.android.freshrss.store.database.models.*
import fr.chenry.android.freshrss.utils.Try
import fr.chenry.android.freshrss.utils.getOrDefault
import nl.komponents.kovenant.*
import nl.komponents.kovenant.functional.bind
import org.joda.time.LocalDateTime
import kotlin.reflect.KProperty
@Database(version = 5, entities = [Account::class, Article::class, Subscription::class])
@TypeConverters(Converters::class)
@GenerateRoomMigrations
abstract class FreshRSSDabatabase: RoomDatabase() {
private val authTokensDelegate = AuthTokensDelegate()
var account: Account by authTokensDelegate
val authTokensExistInDB get() = authTokensDelegate.isInitialized
protected abstract fun getAuthTokensDAO(): AuthTokensDAO
protected abstract fun getArticlesDAO(): ArticlesDAO
protected abstract fun getSubscriptionsDAO(): SubscriptionsDAO
fun getArticleByStreamId(streamId: StreamId) = getArticlesDAO().getByStreamId(streamId)
fun getAccount() = getAuthTokensDAO().getAuthTokens()
fun insertAccount(account: Account) = getAuthTokensDAO().insert(account)
fun getArticlesByStreamId(streamId: StreamId) = getArticlesDAO().getByStreamId(streamId)
fun getArticleById(id: ItemId) = getArticlesDAO().getById(id)
fun getArticleByStreamIdAndUnread(streamId: StreamId) =
getArticlesDAO().getByStreamIdAndUnread(streamId, ReadStatus.UNREAD.name)
......@@ -64,6 +59,7 @@ abstract class FreshRSSDabatabase: RoomDatabase() {
fun updateSubscriptionCount(id: String, count: Int) = getSubscriptionsDAO().updateCount(id, count)
fun updateSubscriptionNewestArticleDate(id: String, newestArticleDate: LocalDateTime) =
getSubscriptionsDAO().updateNewestArticleDate(id, newestArticleDate)
fun incrementSubscriptionCount(id: String) = getSubscriptionsDAO().incrementCount(id)
fun decrementSubscriptionCount(id: String) = getSubscriptionsDAO().decrementCount(id)
fun insertSubscriptionImage(id: String, bitmap: Bitmap) = getSubscriptionsDAO().insertImage(id, bitmap)
......@@ -72,39 +68,4 @@ abstract class FreshRSSDabatabase: RoomDatabase() {
fun getAllUnreadSubcriptions() = getSubscriptionsDAO().getAllUnread()
fun getAllSubcriptionsIds() = getSubscriptionsDAO().getAllIds()
fun getAllSubcriptionsWithImageToUpdate() = getSubscriptionsDAO().withImageToUpdate()
companion object {
private val dbName by lazy {
"${FreshRSSApplication.context.getString(R.string.app_name).toLowerCase()}.db"
}
val instance by lazy {
val instance =
Room
.databaseBuilder(FreshRSSApplication.context, FreshRSSDabatabase::class.java, dbName)
.addMigrations(*FreshRSSDabatabase_Migrations.build())
.build()
instance.authTokensDelegate.fetchAuthtokensFromDB()
instance
}
}
inner class AuthTokensDelegate {
private lateinit var cachedAccount: Account
val isInitialized
get() = ::cachedAccount.isInitialized
operator fun getValue(thisRef: Any?, property: KProperty<*>): Account = cachedAccount
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Account) {
cachedAccount = value
task {getAuthTokensDAO().insert(account)}.getOrDefault(Unit)
}
fun fetchAuthtokensFromDB() {
if(!isInitialized) {
val authTokensFromDB = task {getAuthTokensDAO().getAuthTokens()[0]}.getOrDefault(null)
if(authTokensFromDB != null) cachedAccount = authTokensFromDB
}
}
}
}
\ No newline at end of file
......@@ -2,6 +2,7 @@ package fr.chenry.android.freshrss.store.database.models
import androidx.room.*
import com.github.kittinunf.fuel.core.ResponseDeserializable
import io.reactivex.Flowable
import org.joda.time.LocalDateTime
import java.util.*
......@@ -38,11 +39,13 @@ data class Account(
}
}
val VoidAccount = Account("", "", serverInstance = "localhost")
@Dao
interface AuthTokensDAO {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(account: Account)
@Query("SELECT * FROM accounts")
fun getAuthTokens(): List<Account>
fun getAuthTokens(): Flowable<List<Account>>
}
\ No newline at end of file
package fr.chenry.android.freshrss.store.viewmodels
import androidx.lifecycle.*
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.store.database.models.Account
class AccountVM: ViewModel() {
val liveData: LiveData<Account>
private val source: LiveData<List<Account>>
init {
val flowable = FreshRSSApplication.database.getAccount()
source = flowable.toLiveData()
liveData = MutableLiveData<Account>().apply {
value = flowable.blockingFirst().firstOrNull()
source.observeForever {if(it.isNotEmpty()) value = it.first()}
}
}
}
\ No newline at end of file
......@@ -11,7 +11,7 @@ class SubscriptionArticleVM(articleId: ItemId): ViewModel() {
private val source: LiveData<Articles>
init {
val flowable = FreshRSSDabatabase.instance.getArticleById(articleId)
val flowable = FreshRSSApplication.database.getArticleById(articleId)
val article = flowable.blockingFirst().first()
source = flowable.toLiveData()
......
......@@ -19,7 +19,7 @@ class SubscriptionArticlesVM(streamId: String, readStatus: ReadStatus): ViewMode
subscriptionLiveData.observeForever{if(it.isNotEmpty()) subscription = it.first()}
source = when(readStatus) {
ReadStatus.READ -> FreshRSSApplication.database.getArticleByStreamId(streamId)
ReadStatus.READ -> FreshRSSApplication.database.getArticlesByStreamId(streamId)
ReadStatus.UNREAD -> FreshRSSApplication.database.getArticleByStreamIdAndUnread(streamId)
}
......
......@@ -6,27 +6,26 @@
<path
android:pathData="M54,54m-8.25,0a8.25,8.25 0,1 1,16.5 0a8.25,8.25 0,1 1,-16.5 0"
android:strokeWidth="0.25"
android:fillColor="#c5c6ca"/>
android:fillColor="@color/logo_grey"/>
<path
android:pathData="M22.807,54A31.193,31.193 0,1 1,54 85.193"
android:strokeAlpha="0.3"
android:strokeWidth="6.075"
android:fillColor="#00000000"
android:strokeColor="#c5c6ca"/>
android:strokeColor="@color/logo_grey"/>
<path
android:pathData="m34.101,54a19.899,19.899 0,1 1,19.899 19.899"
android:strokeAlpha="0.3"
android:strokeWidth="6.075"
android:fillColor="#00000000"
android:strokeColor="#c5c6ca"/>
android:strokeColor="@color/logo_grey"/>
<path
android:pathData="M54,22.807A31.193,31.193 0,0 1,85.193 54"
android:strokeWidth="6.075"
android:fillColor="#00000000"
android:strokeColor="#c5c6ca"/>
android:strokeColor="@color/logo_grey"/>
<path
android:pathData="m54,34.101a19.899,19.899 0,0 1,19.899 19.899"
android:strokeWidth="6.075"
android:fillColor="#00000000"
android:strokeColor="#c5c6ca"/>
android:strokeColor="@color/logo_grey"/>
</vector>
......@@ -5,6 +5,6 @@
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path android:fillColor="#41444f"
<path android:fillColor="@color/logo_obsidian"
android:pathData="M0,0h108v108h-108z"/>
</vector>
......@@ -4,8 +4,19 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.TestActivity">
<TextView android:id="@+id/test_text"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center"
tools:context=".activities.StartActivity"
android:background="@color/logo_obsidian">
<TextView
android:id="@+id/activity_start_welcome_text"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="wrap_content"
android:text="@string/good_morning_user"
android:drawableTop="@drawable/ic_launcher_foreground"
android:gravity="center"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@color/logo_grey"/>
</LinearLayout>
\ No newline at end of file
......@@ -7,4 +7,6 @@
<color name="grey">#666666</color>
<color name="error">#A00</color>
<color name="light_grey">#9E9E9E</color>
<color name="logo_obsidian">#41444f</color>
<color name="logo_grey">#c5c6ca</color>
</resources>
......@@ -7,4 +7,5 @@
<dimen name="subscription_section_v_padding">4dp</dimen>
<dimen name="subscription_section_h_padding">8dp</dimen>
<dimen name="hline_weight">2dp</dimen>
<dimen name="fab_margin">16dp</dimen>
</resources>
......@@ -19,6 +19,14 @@
<string name="title_all">All</string>
<string name="title_unread">Unread</string>
<!-- Notifications -->
<string name="notification_refresh_title">Refreshing your RSS feeds</string>
<string name="notification_refresh_description">FreshRSS is fetching your content from the server</string>
<string name="notification_refresh_failed_title">Refreshing failed</string>
<string name="notification_refresh_failed_description">FreshRSS failed to retrieve content from your FreshRSS server</string>
<string name="notification_channel_refresh_title">Refresh events</string>
<string name="notification_channel_refresh_description">Events occurring when trying to fetch content from your FreshRSS server</string>
<!-- Strings related to subscriptions -->
<string name="subscription_title_label">Title</string>
<string name="subscription_unread_count_label">0</string>
......@@ -57,11 +65,9 @@
<string name="human_time_grouping_this_year">This year</string>
<string name="human_time_grouping_old_articles">Old articles</string>
<!-- Notifications -->
<string name="notification_refresh_title">Refreshing your RSS feeds</string>
<string name="notification_refresh_description">FreshRSS is fetching your content from the server</string>
<string name="notification_refresh_failed_title">Refreshing failed</string>
<string name="notification_refresh_failed_description">FreshRSS failed to retrieve content from your FreshRSS server</string>
<string name="notification_channel_refresh_title">Refresh events</string>
<string name="notification_channel_refresh_description">Events occurring when trying to fetch content from your FreshRSS server</string>
<string name="title_activity_start">StartActivity</string>
<string name="hello_user">Hello%s</string>
<string name="good_morning_user">Good morning%s</string>
<string name="good_evening_user">Good evening%s</string>
<string name="splash_screen">Splash screen</string>
</resources>
......@@ -7,5 +7,11 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment