Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added e-ink mode setting. When enabled, navigation transitions and image crossfade are disabled, and tap zones (tap the top or bottom half of the screen) allow paging through articles without scrolling. Intended for e-ink readers.

### Changed

- Changed the way unread bookmarks are determined. The behavior now matches that of the Readeck web app. This means that unread bookmarks are bookmarks that have not been archived. Closes #166 and #169.
Expand Down
220 changes: 220 additions & 0 deletions app/src/main/assets/html_template_eink.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<style>
/* Body */
html {
font-size: 62.5%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
}

body {
font-size: 1.8rem;
font-weight: 500;
line-height: 1.618;
max-width: 38em;
margin: auto;
color: #000000;
background-color: #ffffff;
padding: 13px;
}

@media (max-width: 684px) {
body {
font-size: 1.53rem;
}
}
@media (max-width: 382px) {
body {
font-size: 1.35rem;
}
}
h1, h2, h3, h4, h5, h6 {
line-height: 1.1;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
font-weight: 700;
margin-top: 3rem;
margin-bottom: 1.5rem;
overflow-wrap: break-word;
word-wrap: break-word;
-ms-word-break: break-all;
word-break: break-word;
}

h1 { font-size: 2.35em; }
h2 { font-size: 2em; }
h3 { font-size: 1.75em; }
h4 { font-size: 1.5em; }
h5 { font-size: 1.25em; }
h6 { font-size: 1em; }

p {
margin-top: 0px;
margin-bottom: 2.5rem;
}

small, sub, sup {
font-size: 75%;
}

hr {
border-color: #000000;
}

a {
text-decoration: underline;
color: #000000;
}
a:visited {
color: #000000;
}
a:hover {
color: #000000;
}

ul {
padding-left: 1.4em;
margin-top: 0px;
margin-bottom: 2.5rem;
}

li {
margin-bottom: 0.4em;
}

blockquote {
margin-left: 0px;
margin-right: 0px;
padding-left: 1em;
padding-top: 0.8em;
padding-bottom: 0.8em;
padding-right: 0.8em;
border-left: 5px solid #000000;
margin-bottom: 2.5rem;
background-color: transparent;
}

blockquote p {
margin-bottom: 0;
}

img, video {
height: auto;
max-width: 100%;
margin-top: 0px;
margin-bottom: 2.5rem;
}

/* Pre and Code */
pre {
background-color: #eeeeee;
display: block;
padding: 1em;
overflow-x: auto;
margin-top: 0px;
margin-bottom: 2.5rem;
font-size: 0.9em;
}

code, kbd, samp {
font-size: 0.9em;
font-weight: 500;
padding: 0 0.5em;
background-color: #eeeeee;
white-space: pre-wrap;
}

pre > code {
padding: 0;
background-color: transparent;
white-space: pre;
font-size: 1em;
}

/* Tables */
table {
text-align: justify;
width: 100%;
border-collapse: collapse;
margin-bottom: 2rem;
}

td, th {
padding: 0.5em;
border-bottom: 1px solid #000000;
}

/* Buttons, forms and input */
input, textarea {
border: 1px solid #000000;
}
input:focus, textarea:focus {
border: 1px solid #000000;
}

textarea {
width: 100%;
}

.button, button, input[type=submit], input[type=reset], input[type=button], input[type=file]::file-selector-button {
display: inline-block;
padding: 5px 10px;
text-align: center;
text-decoration: none;
white-space: nowrap;
background-color: #000000;
color: #ffffff;
border-radius: 0;
border: 1px solid #000000;
cursor: pointer;
box-sizing: border-box;
}
.button[disabled], button[disabled], input[type=submit][disabled], input[type=reset][disabled], input[type=button][disabled], input[type=file]::file-selector-button[disabled] {
cursor: default;
opacity: 0.5;
}
.button:hover, button:hover, input[type=submit]:hover, input[type=reset]:hover, input[type=button]:hover, input[type=file]::file-selector-button:hover {
background-color: #000000;
color: #ffffff;
outline: 0;
}
.button:focus-visible, button:focus-visible, input[type=submit]:focus-visible, input[type=reset]:focus-visible, input[type=button]:focus-visible, input[type=file]::file-selector-button:focus-visible {
outline-style: solid;
outline-width: 2px;
}

textarea, select, input {
color: #000000;
padding: 6px 10px;
margin-bottom: 10px;
background-color: #ffffff;
border: 1px solid #000000;
border-radius: 0;
box-shadow: none;
box-sizing: border-box;
}
textarea:focus, select:focus, input:focus {
border: 1px solid #000000;
outline: 0;
}

input[type=checkbox]:focus {
outline: 1px dotted #000000;
}

label, legend, fieldset {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
}

</style>
</head>
<body>
<div class="container">
%s
</div>
</body>
</html>
21 changes: 17 additions & 4 deletions app/src/main/java/de/readeckapp/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
Expand Down Expand Up @@ -56,6 +61,7 @@ class MainActivity : ComponentActivity() {
setContent {
val viewModel = hiltViewModel<MainViewModel>()
val theme = viewModel.theme.collectAsState()
val eInkMode = viewModel.eInkMode.collectAsState()
val navController = rememberNavController()
intentState = remember { mutableStateOf(intent) }
val scope = rememberCoroutineScope()
Expand Down Expand Up @@ -89,8 +95,8 @@ class MainActivity : ComponentActivity() {
else -> theme.value
}

ReadeckAppTheme(theme = themeValue) {
ReadeckNavHost(navController)
ReadeckAppTheme(theme = themeValue, eInkMode = eInkMode.value) {
ReadeckNavHost(navController, eInkMode = eInkMode.value)
}
}
}
Expand All @@ -103,8 +109,15 @@ class MainActivity : ComponentActivity() {

@SuppressLint("WrongStartDestinationType")
@Composable
fun ReadeckNavHost(navController: NavHostController) {
NavHost(navController = navController, startDestination = BookmarkListRoute()) {
fun ReadeckNavHost(navController: NavHostController, eInkMode: Boolean = false) {
NavHost(
navController = navController,
startDestination = BookmarkListRoute(),
enterTransition = { if (eInkMode) EnterTransition.None else fadeIn(animationSpec = tween(700)) },
exitTransition = { if (eInkMode) ExitTransition.None else fadeOut(animationSpec = tween(700)) },
popEnterTransition = { if (eInkMode) EnterTransition.None else fadeIn(animationSpec = tween(700)) },
popExitTransition = { if (eInkMode) ExitTransition.None else fadeOut(animationSpec = tween(700)) },
) {
composable<BookmarkListRoute> { BookmarkListScreen(navController) }
composable<SettingsRoute> { SettingsScreen(navController) }
composable<AccountSettingsRoute> { AccountSettingsScreen(navController) }
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/de/readeckapp/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@ class MainViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5000),
initialValue = Theme.SYSTEM
)

val eInkMode = settingsDataStore.eInkModeFlow.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = false
)
}
1 change: 1 addition & 0 deletions app/src/main/java/de/readeckapp/domain/model/Template.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ sealed interface Template {
const val LIGHT_TEMPLATE_FILE = "html_template_light.html"
const val DARK_TEMPLATE_FILE = "html_template_dark.html"
const val SEPIA_TEMPLATE_FILE = "html_template_sepia.html"
const val EINK_TEMPLATE_FILE = "html_template_eink.html"
}
}
3 changes: 3 additions & 0 deletions app/src/main/java/de/readeckapp/io/prefs/SettingsDataStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface SettingsDataStore {
val urlFlow: StateFlow<String?>
val themeFlow: StateFlow<String?>
val zoomFactorFlow: StateFlow<Int>
val eInkModeFlow: StateFlow<Boolean>

fun saveUsername(username: String)
fun saveToken(token: String)
Expand All @@ -34,6 +35,8 @@ interface SettingsDataStore {
suspend fun setSyncReadProgressEnabled(enabled: Boolean)
suspend fun isScrollToProgressEnabled(): Boolean
suspend fun setScrollToProgressEnabled(enabled: Boolean)
suspend fun isEInkModeEnabled(): Boolean
suspend fun setEInkModeEnabled(enabled: Boolean)
suspend fun saveTheme(theme: Theme)
suspend fun getTheme(): Theme
suspend fun getZoomFactor(): Int
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/java/de/readeckapp/io/prefs/SettingsDataStoreImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class SettingsDataStoreImpl @Inject constructor(@ApplicationContext private val
private val KEY_ZOOM_FACTOR = intPreferencesKey("zoom_factor")
private val KEY_SYNC_READ_PROGRESS = booleanPreferencesKey("sync_read_progress")
private val KEY_SCROLL_TO_PROGRESS = booleanPreferencesKey("scroll_to_progress")
private val KEY_EINK_MODE = booleanPreferencesKey("eink_mode")
private val KEY_DEFAULT_FILTER = stringPreferencesKey("default_filter")

override fun saveUsername(username: String) {
Expand Down Expand Up @@ -166,6 +167,16 @@ class SettingsDataStoreImpl @Inject constructor(@ApplicationContext private val
}
}

override suspend fun isEInkModeEnabled(): Boolean {
return encryptedSharedPreferences.getBoolean(KEY_EINK_MODE.name, false)
}

override suspend fun setEInkModeEnabled(enabled: Boolean) {
encryptedSharedPreferences.edit {
putBoolean(KEY_EINK_MODE.name, enabled)
}
}

override suspend fun getDefaultFilter(): DefaultFilter {
return encryptedSharedPreferences.getString(KEY_DEFAULT_FILTER.name, DefaultFilter.ALL.name)?.let {
DefaultFilter.valueOf(it)
Expand All @@ -184,6 +195,9 @@ class SettingsDataStoreImpl @Inject constructor(@ApplicationContext private val
override val authStateFlow = getStringFlow(KEY_AUTH_STATE.name, null)
override val themeFlow = getStringFlow(KEY_THEME.name, Theme.SYSTEM.name)
override val zoomFactorFlow = getIntFlow(KEY_ZOOM_FACTOR.name, 100)
override val eInkModeFlow = preferenceFlow(KEY_EINK_MODE.name) {
encryptedSharedPreferences.getBoolean(KEY_EINK_MODE.name, false)
}

init {
// Migration: remove legacy password if it exists
Expand Down
Loading