mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-31 21:41:33 +00:00
Merge branch 'sictiru'
* sictiru: (31 commits) Android v13.1.0 #1811 Verify error handling and show loading indicator to the user when adding a feed. Fix broken swipe to refresh dependency Use plugin information from buildSrc Use dependencies and constants from buildSrc Add buildSrc for dependency management Convert Groovy to Kotlin. Update dependencies Convert Groovy to Kotlin Update sub service to cancel job when requested. Execute on the IO dispatcher. Await for sub service termination Use coroutines in sub services Update proguard rules Enable nonFinalResIds Enable non transitive R classes Update java version for the android GHA Upgrade dependencies #1794 Add auto fill hints for easier login Import Remove context from blur db helper Intel dialog row tweak ...
This commit is contained in:
commit
25a5929f56
81 changed files with 2090 additions and 708 deletions
2
.github/workflows/android-actions.yml
vendored
2
.github/workflows/android-actions.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
|
|
||||||
- name: Unit Test
|
- name: Unit Test
|
||||||
run: ./gradlew -Pci --console=plain :app:testDebugUnitTest
|
run: ./gradlew -Pci --console=plain :app:testDebugUnitTest
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
plugins {
|
|
||||||
id 'com.android.test'
|
|
||||||
id 'org.jetbrains.kotlin.android'
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace 'com.newsblur.benchmark'
|
|
||||||
compileSdk 33
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "1.8"
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk 23
|
|
||||||
targetSdk 33
|
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
// This benchmark buildType is used for benchmarking, and should function like your
|
|
||||||
// release build (for example, with minification on). It's signed with a debug key
|
|
||||||
// for easy local/CI testing.
|
|
||||||
benchmark {
|
|
||||||
debuggable = true
|
|
||||||
signingConfig = debug.signingConfig
|
|
||||||
matchingFallbacks = ["release"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
targetProjectPath = ":app"
|
|
||||||
experimentalProperties["android.experimental.self-instrumenting"] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation 'androidx.test.ext:junit:1.1.3'
|
|
||||||
implementation 'androidx.test.espresso:espresso-core:3.4.0'
|
|
||||||
implementation 'androidx.test.uiautomator:uiautomator:2.2.0'
|
|
||||||
implementation 'androidx.benchmark:benchmark-macro-junit4:1.1.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
androidComponents {
|
|
||||||
beforeVariants(selector().all()) {
|
|
||||||
enabled = buildType == "benchmark"
|
|
||||||
}
|
|
||||||
}
|
|
49
clients/android/NewsBlur/app/benchmark/build.gradle.kts
Normal file
49
clients/android/NewsBlur/app/benchmark/build.gradle.kts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
plugins {
|
||||||
|
id(Plugins.androidTest)
|
||||||
|
kotlin(Plugins.kotlinAndroid)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = Const.namespaceBenchmark
|
||||||
|
compileSdk = Config.compileSdk
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = Config.javaVersion
|
||||||
|
targetCompatibility = Config.javaVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = Config.minSdk
|
||||||
|
targetSdk = Config.targetSdk
|
||||||
|
|
||||||
|
testInstrumentationRunner = Config.androidTestInstrumentation
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
// This benchmark buildType is used for benchmarking, and should function like your
|
||||||
|
// release build (for example, with minification on). It's signed with a debug key
|
||||||
|
// for easy local/CI testing.
|
||||||
|
maybeCreate(Const.benchmark)
|
||||||
|
getByName(Const.benchmark) {
|
||||||
|
isDebuggable = true
|
||||||
|
signingConfig = signingConfigs.getByName(Const.debug)
|
||||||
|
matchingFallbacks += listOf(Const.release)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
targetProjectPath = ":app"
|
||||||
|
experimentalProperties[Const.selfInstrumenting] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(Dependencies.junitExt)
|
||||||
|
implementation(Dependencies.espressoCore)
|
||||||
|
implementation(Dependencies.uiAutomator)
|
||||||
|
implementation(Dependencies.benchmarkMacroJunit4)
|
||||||
|
}
|
||||||
|
|
||||||
|
androidComponents {
|
||||||
|
beforeVariants(selector().all()) {
|
||||||
|
it.enabled = it.buildType == Const.benchmark
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,74 +0,0 @@
|
||||||
plugins {
|
|
||||||
id 'com.android.application'
|
|
||||||
id 'org.jetbrains.kotlin.android'
|
|
||||||
id 'org.jetbrains.kotlin.kapt'
|
|
||||||
id 'com.google.dagger.hilt.android'
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace 'com.newsblur'
|
|
||||||
compileSdk 33
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
applicationId "com.newsblur"
|
|
||||||
minSdk 23
|
|
||||||
targetSdk 33
|
|
||||||
versionCode 212
|
|
||||||
versionName "13.0.0"
|
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
debug {
|
|
||||||
minifyEnabled false
|
|
||||||
shrinkResources false
|
|
||||||
}
|
|
||||||
benchmark {
|
|
||||||
signingConfig signingConfigs.debug
|
|
||||||
matchingFallbacks = ['release']
|
|
||||||
debuggable false
|
|
||||||
proguardFiles('benchmark-rules.pro')
|
|
||||||
}
|
|
||||||
release {
|
|
||||||
minifyEnabled true
|
|
||||||
shrinkResources true
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
packagingOptions {
|
|
||||||
resources.excludes.add("META-INF/*")
|
|
||||||
}
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = '1.8'
|
|
||||||
}
|
|
||||||
buildFeatures.viewBinding = true
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.5.5'
|
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
|
|
||||||
implementation 'com.google.code.gson:gson:2.10'
|
|
||||||
implementation 'com.android.billingclient:billing:5.1.0'
|
|
||||||
implementation 'com.google.android.play:core:1.10.3'
|
|
||||||
implementation "com.google.android.material:material:1.7.0"
|
|
||||||
implementation "androidx.preference:preference-ktx:1.2.0"
|
|
||||||
implementation "androidx.browser:browser:1.4.0"
|
|
||||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
|
|
||||||
implementation 'androidx.lifecycle:lifecycle-process:2.5.1'
|
|
||||||
implementation 'androidx.core:core-splashscreen:1.0.0'
|
|
||||||
implementation "com.google.dagger:hilt-android:2.44.2"
|
|
||||||
kapt "com.google.dagger:hilt-compiler:2.44.2"
|
|
||||||
implementation "androidx.profileinstaller:profileinstaller:1.2.2"
|
|
||||||
|
|
||||||
testImplementation "junit:junit:4.13.2"
|
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
|
||||||
}
|
|
75
clients/android/NewsBlur/app/build.gradle.kts
Normal file
75
clients/android/NewsBlur/app/build.gradle.kts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
plugins {
|
||||||
|
id(Plugins.androidApplication)
|
||||||
|
kotlin(Plugins.kotlinAndroid)
|
||||||
|
kotlin(Plugins.kotlinKapt)
|
||||||
|
id(Plugins.hiltAndroid)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = Const.namespace
|
||||||
|
compileSdk = Config.compileSdk
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = Const.namespace
|
||||||
|
minSdk = Config.minSdk
|
||||||
|
targetSdk = Config.targetSdk
|
||||||
|
versionCode = Config.versionCode
|
||||||
|
versionName = Config.versionName
|
||||||
|
|
||||||
|
testInstrumentationRunner = Config.androidTestInstrumentation
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
getByName(Const.debug) {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
isShrinkResources = false
|
||||||
|
}
|
||||||
|
maybeCreate(Const.benchmark)
|
||||||
|
getByName(Const.benchmark) {
|
||||||
|
signingConfig = signingConfigs.getByName(Const.debug)
|
||||||
|
matchingFallbacks += listOf(Const.release)
|
||||||
|
isDebuggable = false
|
||||||
|
proguardFiles(Const.benchmarkProguard)
|
||||||
|
}
|
||||||
|
getByName(Const.release) {
|
||||||
|
isMinifyEnabled = true
|
||||||
|
isShrinkResources = true
|
||||||
|
proguardFiles(getDefaultProguardFile(Const.defaultProguard), Const.appProguard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
packaging {
|
||||||
|
resources.excludes.add("META-INF/*")
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = Config.javaVersion
|
||||||
|
targetCompatibility = Config.javaVersion
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(Dependencies.fragment)
|
||||||
|
implementation(Dependencies.recyclerView)
|
||||||
|
implementation(Dependencies.swipeRefreshLayout)
|
||||||
|
implementation(Dependencies.okHttp)
|
||||||
|
implementation(Dependencies.gson)
|
||||||
|
implementation(Dependencies.billing)
|
||||||
|
implementation(Dependencies.playCore)
|
||||||
|
implementation(Dependencies.material)
|
||||||
|
implementation(Dependencies.preference)
|
||||||
|
implementation(Dependencies.browser)
|
||||||
|
implementation(Dependencies.lifecycleRuntime)
|
||||||
|
implementation(Dependencies.lifecycleProcess)
|
||||||
|
implementation(Dependencies.splashScreen)
|
||||||
|
implementation(Dependencies.hiltAndroid)
|
||||||
|
kapt(Dependencies.hiltCompiler)
|
||||||
|
implementation(Dependencies.profileInstaller)
|
||||||
|
|
||||||
|
testImplementation(Dependencies.junit)
|
||||||
|
testImplementation(Dependencies.mockk)
|
||||||
|
|
||||||
|
androidTestImplementation(Dependencies.junitExt)
|
||||||
|
androidTestImplementation(Dependencies.espressoCore)
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
# Add project specific ProGuard rules here.
|
# Add project specific ProGuard rules here.
|
||||||
# You can control the set of applied configuration files using the
|
# You can control the set of applied configuration files using the
|
||||||
# proguardFiles setting in build.gradle.
|
# proguardFiles setting in build.gradle.kts.
|
||||||
#
|
#
|
||||||
# For more details, see
|
# For more details, see
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
@ -41,3 +41,7 @@
|
||||||
# can be commented out to help diagnose shrinkage errors.
|
# can be commented out to help diagnose shrinkage errors.
|
||||||
-dontwarn **
|
-dontwarn **
|
||||||
-dontnote **
|
-dontnote **
|
||||||
|
|
||||||
|
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
|
||||||
|
-keep, allowobfuscation, allowshrinking class com.google.gson.reflect.TypeToken
|
||||||
|
-keep, allowobfuscation, allowshrinking class * extends com.google.gson.reflect.TypeToken
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
package="com.newsblur">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
|
@ -101,44 +101,44 @@ abstract public class FeedChooser extends NbActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == android.R.id.home) {
|
||||||
case android.R.id.home:
|
finish();
|
||||||
finish();
|
return true;
|
||||||
return true;
|
} else if (item.getItemId() == R.id.menu_sort_order_ascending) {
|
||||||
case R.id.menu_sort_order_ascending:
|
replaceListOrderFilter(ListOrderFilter.ASCENDING);
|
||||||
replaceListOrderFilter(ListOrderFilter.ASCENDING);
|
return true;
|
||||||
return true;
|
} else if (item.getItemId() == R.id.menu_sort_order_descending) {
|
||||||
case R.id.menu_sort_order_descending:
|
replaceListOrderFilter(ListOrderFilter.DESCENDING);
|
||||||
replaceListOrderFilter(ListOrderFilter.DESCENDING);
|
return true;
|
||||||
return true;
|
} else if (item.getItemId() == R.id.menu_sort_by_name) {
|
||||||
case R.id.menu_sort_by_name:
|
replaceFeedOrderFilter(FeedOrderFilter.NAME);
|
||||||
replaceFeedOrderFilter(FeedOrderFilter.NAME);
|
return true;
|
||||||
return true;
|
} else if (item.getItemId() == R.id.menu_sort_by_subs) {
|
||||||
case R.id.menu_sort_by_subs:
|
replaceFeedOrderFilter(FeedOrderFilter.SUBSCRIBERS);
|
||||||
replaceFeedOrderFilter(FeedOrderFilter.SUBSCRIBERS);
|
return true;
|
||||||
return true;
|
} else if (item.getItemId() == R.id.menu_sort_by_recent_story) {
|
||||||
case R.id.menu_sort_by_recent_story:
|
replaceFeedOrderFilter(FeedOrderFilter.RECENT_STORY);
|
||||||
replaceFeedOrderFilter(FeedOrderFilter.RECENT_STORY);
|
return true;
|
||||||
return true;
|
} else if (item.getItemId() == R.id.menu_sort_by_stories_month) {
|
||||||
case R.id.menu_sort_by_stories_month:
|
replaceFeedOrderFilter(FeedOrderFilter.STORIES_MONTH);
|
||||||
replaceFeedOrderFilter(FeedOrderFilter.STORIES_MONTH);
|
return true;
|
||||||
return true;
|
} else if (item.getItemId() == R.id.menu_sort_by_number_opens) {
|
||||||
case R.id.menu_sort_by_number_opens:
|
replaceFeedOrderFilter(FeedOrderFilter.OPENS);
|
||||||
replaceFeedOrderFilter(FeedOrderFilter.OPENS);
|
return true;
|
||||||
return true;
|
} else if (item.getItemId() == R.id.menu_folder_view_nested) {
|
||||||
case R.id.menu_folder_view_nested:
|
replaceFolderView(FolderViewFilter.NESTED);
|
||||||
replaceFolderView(FolderViewFilter.NESTED);
|
return true;
|
||||||
return true;
|
} else if (item.getItemId() == R.id.menu_folder_view_flat) {
|
||||||
case R.id.menu_folder_view_flat:
|
replaceFolderView(FolderViewFilter.FLAT);
|
||||||
replaceFolderView(FolderViewFilter.FLAT);
|
return true;
|
||||||
return true;
|
} else if (item.getItemId() == R.id.menu_widget_background_default) {
|
||||||
case R.id.menu_widget_background_default:
|
setWidgetBackground(WidgetBackground.DEFAULT);
|
||||||
setWidgetBackground(WidgetBackground.DEFAULT);
|
return true;
|
||||||
return true;
|
} else if (item.getItemId() == R.id.menu_widget_background_transparent) {
|
||||||
case R.id.menu_widget_background_transparent:
|
setWidgetBackground(WidgetBackground.TRANSPARENT);
|
||||||
setWidgetBackground(WidgetBackground.TRANSPARENT);
|
return true;
|
||||||
default:
|
} else {
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ public abstract class ItemsList extends NbActivity implements ReadingActionListe
|
||||||
// this is not strictly necessary, since our first refresh with the fs will swap in
|
// this is not strictly necessary, since our first refresh with the fs will swap in
|
||||||
// the correct session, but that can be delayed by sync backup, so we try here to
|
// the correct session, but that can be delayed by sync backup, so we try here to
|
||||||
// reduce UI lag, or in case somehow we got redisplayed in a zero-story state
|
// reduce UI lag, or in case somehow we got redisplayed in a zero-story state
|
||||||
feedUtils.prepareReadingSession(fs, false);
|
feedUtils.prepareReadingSession(this, fs, false);
|
||||||
if (getIntent().getBooleanExtra(EXTRA_WIDGET_STORY, false)) {
|
if (getIntent().getBooleanExtra(EXTRA_WIDGET_STORY, false)) {
|
||||||
String hash = (String) getIntent().getSerializableExtra(EXTRA_STORY_HASH);
|
String hash = (String) getIntent().getSerializableExtra(EXTRA_STORY_HASH);
|
||||||
UIUtils.startReadingActivity(fs, hash, this);
|
UIUtils.startReadingActivity(fs, hash, this);
|
||||||
|
@ -206,7 +206,7 @@ public abstract class ItemsList extends NbActivity implements ReadingActionListe
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
// set the next session on the parent activity
|
// set the next session on the parent activity
|
||||||
fs = session.getFeedSet();
|
fs = session.getFeedSet();
|
||||||
feedUtils.prepareReadingSession(fs, false);
|
feedUtils.prepareReadingSession(this, fs, false);
|
||||||
triggerSync();
|
triggerSync();
|
||||||
|
|
||||||
// set the next session on the child activity
|
// set the next session on the child activity
|
||||||
|
@ -248,7 +248,7 @@ public abstract class ItemsList extends NbActivity implements ReadingActionListe
|
||||||
String oldQuery = fs.getSearchQuery();
|
String oldQuery = fs.getSearchQuery();
|
||||||
fs.setSearchQuery(q);
|
fs.setSearchQuery(q);
|
||||||
if (!TextUtils.equals(q, oldQuery)) {
|
if (!TextUtils.equals(q, oldQuery)) {
|
||||||
feedUtils.prepareReadingSession(fs, true);
|
feedUtils.prepareReadingSession(this, fs, true);
|
||||||
triggerSync();
|
triggerSync();
|
||||||
itemSetFragment.resetEmptyState();
|
itemSetFragment.resetEmptyState();
|
||||||
itemSetFragment.hasUpdated();
|
itemSetFragment.hasUpdated();
|
||||||
|
@ -278,7 +278,7 @@ public abstract class ItemsList extends NbActivity implements ReadingActionListe
|
||||||
|
|
||||||
protected void restartReadingSession() {
|
protected void restartReadingSession() {
|
||||||
NBSyncService.resetFetchState(fs);
|
NBSyncService.resetFetchState(fs);
|
||||||
feedUtils.prepareReadingSession(fs, true);
|
feedUtils.prepareReadingSession(this, fs, true);
|
||||||
triggerSync();
|
triggerSync();
|
||||||
itemSetFragment.resetEmptyState();
|
itemSetFragment.resetEmptyState();
|
||||||
itemSetFragment.hasUpdated();
|
itemSetFragment.hasUpdated();
|
||||||
|
|
|
@ -10,10 +10,6 @@ import android.graphics.Bitmap;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Trace;
|
import android.os.Trace;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
import androidx.appcompat.widget.PopupMenu;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
@ -22,13 +18,23 @@ import android.view.View;
|
||||||
import android.view.View.OnKeyListener;
|
import android.view.View.OnKeyListener;
|
||||||
import android.widget.AbsListView;
|
import android.widget.AbsListView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.appcompat.widget.PopupMenu;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
import com.newsblur.R;
|
import com.newsblur.R;
|
||||||
import com.newsblur.database.BlurDatabaseHelper;
|
import com.newsblur.database.BlurDatabaseHelper;
|
||||||
import com.newsblur.databinding.ActivityMainBinding;
|
import com.newsblur.databinding.ActivityMainBinding;
|
||||||
import com.newsblur.delegate.MainContextMenuDelegate;
|
import com.newsblur.delegate.MainContextMenuDelegate;
|
||||||
import com.newsblur.delegate.MainContextMenuDelegateImpl;
|
import com.newsblur.delegate.MainContextMenuDelegateImpl;
|
||||||
import com.newsblur.fragment.FeedIntelligenceSelectorFragment;
|
import com.newsblur.fragment.FeedSelectorFragment;
|
||||||
|
import com.newsblur.fragment.FeedsShortcutFragment;
|
||||||
import com.newsblur.fragment.FolderListFragment;
|
import com.newsblur.fragment.FolderListFragment;
|
||||||
|
import com.newsblur.keyboard.KeyboardEvent;
|
||||||
|
import com.newsblur.keyboard.KeyboardListener;
|
||||||
|
import com.newsblur.keyboard.KeyboardManager;
|
||||||
import com.newsblur.service.BootReceiver;
|
import com.newsblur.service.BootReceiver;
|
||||||
import com.newsblur.service.NBSyncService;
|
import com.newsblur.service.NBSyncService;
|
||||||
import com.newsblur.util.AppConstants;
|
import com.newsblur.util.AppConstants;
|
||||||
|
@ -45,7 +51,7 @@ import javax.inject.Inject;
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class Main extends NbActivity implements StateChangedListener, SwipeRefreshLayout.OnRefreshListener, AbsListView.OnScrollListener, PopupMenu.OnMenuItemClickListener {
|
public class Main extends NbActivity implements StateChangedListener, SwipeRefreshLayout.OnRefreshListener, AbsListView.OnScrollListener, PopupMenu.OnMenuItemClickListener, KeyboardListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedUtils feedUtils;
|
FeedUtils feedUtils;
|
||||||
|
@ -55,10 +61,12 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
||||||
|
|
||||||
public static final String EXTRA_FORCE_SHOW_FEED_ID = "force_show_feed_id";
|
public static final String EXTRA_FORCE_SHOW_FEED_ID = "force_show_feed_id";
|
||||||
|
|
||||||
private FolderListFragment folderFeedList;
|
private FolderListFragment folderFeedList;
|
||||||
|
private FeedSelectorFragment feedSelectorFragment;
|
||||||
private boolean wasSwipeEnabled = false;
|
private boolean wasSwipeEnabled = false;
|
||||||
private ActivityMainBinding binding;
|
private ActivityMainBinding binding;
|
||||||
private MainContextMenuDelegate contextMenuDelegate;
|
private MainContextMenuDelegate contextMenuDelegate;
|
||||||
|
private KeyboardManager keyboardManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
@ -69,7 +77,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
||||||
getWindow().setBackgroundDrawableResource(android.R.color.transparent);
|
getWindow().setBackgroundDrawableResource(android.R.color.transparent);
|
||||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||||
contextMenuDelegate = new MainContextMenuDelegateImpl(this, dbHelper);
|
contextMenuDelegate = new MainContextMenuDelegateImpl(this, dbHelper);
|
||||||
setContentView(binding.getRoot());
|
keyboardManager = new KeyboardManager();
|
||||||
|
setContentView(binding.getRoot());
|
||||||
|
|
||||||
// set the status bar to an generic loading message when the activity is first created so
|
// set the status bar to an generic loading message when the activity is first created so
|
||||||
// that something is displayed while the service warms up
|
// that something is displayed while the service warms up
|
||||||
|
@ -82,7 +91,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
||||||
|
|
||||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||||
folderFeedList = (FolderListFragment) fragmentManager.findFragmentByTag("folderFeedListFragment");
|
folderFeedList = (FolderListFragment) fragmentManager.findFragmentByTag("folderFeedListFragment");
|
||||||
((FeedIntelligenceSelectorFragment) fragmentManager.findFragmentByTag("feedIntelligenceSelector")).setState(folderFeedList.currentState);
|
feedSelectorFragment = ((FeedSelectorFragment) fragmentManager.findFragmentByTag("feedIntelligenceSelector"));
|
||||||
|
feedSelectorFragment.setState(folderFeedList.currentState);
|
||||||
|
|
||||||
// make sure the interval sync is scheduled, since we are the root Activity
|
// make sure the interval sync is scheduled, since we are the root Activity
|
||||||
BootReceiver.scheduleSyncService(this);
|
BootReceiver.scheduleSyncService(this);
|
||||||
|
@ -127,12 +137,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
||||||
// Check whether it's a shortcut intent
|
// Check whether it's a shortcut intent
|
||||||
String shortcutExtra = getIntent().getStringExtra(ShortcutUtils.SHORTCUT_EXTRA);
|
String shortcutExtra = getIntent().getStringExtra(ShortcutUtils.SHORTCUT_EXTRA);
|
||||||
if (shortcutExtra != null && shortcutExtra.startsWith(ShortcutUtils.SHORTCUT_ALL_STORIES)) {
|
if (shortcutExtra != null && shortcutExtra.startsWith(ShortcutUtils.SHORTCUT_ALL_STORIES)) {
|
||||||
Intent intent = new Intent(this, AllStoriesItemsList.class);
|
boolean isAllStoriesSearch = shortcutExtra.equals(ShortcutUtils.SHORTCUT_ALL_STORIES_SEARCH);
|
||||||
intent.putExtra(ItemsList.EXTRA_FEED_SET, FeedSet.allFeeds());
|
openAllStories(isAllStoriesSearch);
|
||||||
if (shortcutExtra.equals(ShortcutUtils.SHORTCUT_ALL_STORIES_SEARCH)) {
|
|
||||||
intent.putExtra(ItemsList.EXTRA_VISIBLE_SEARCH, true);
|
|
||||||
}
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Trace.endSection();
|
Trace.endSection();
|
||||||
|
@ -177,10 +183,17 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
||||||
updateStatusIndicators();
|
updateStatusIndicators();
|
||||||
folderFeedList.pushUnreadCounts();
|
folderFeedList.pushUnreadCounts();
|
||||||
folderFeedList.checkOpenFolderPreferences();
|
folderFeedList.checkOpenFolderPreferences();
|
||||||
|
keyboardManager.addListener(this);
|
||||||
triggerSync();
|
triggerSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
keyboardManager.removeListener();
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void changedState(StateFilter state) {
|
public void changedState(StateFilter state) {
|
||||||
if ( !( (state == StateFilter.ALL) ||
|
if ( !( (state == StateFilter.ALL) ||
|
||||||
(state == StateFilter.SOME) ||
|
(state == StateFilter.SOME) ||
|
||||||
|
@ -211,7 +224,27 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
||||||
if ((updateType & UPDATE_METADATA) != 0) {
|
if ((updateType & UPDATE_METADATA) != 0) {
|
||||||
folderFeedList.hasUpdated();
|
folderFeedList.hasUpdated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||||
|
if (KeyboardManager.hasHardwareKeyboard(this)) {
|
||||||
|
boolean isKnownKeyCode = keyboardManager.isKnownKeyCode(keyCode);
|
||||||
|
if (isKnownKeyCode) return true;
|
||||||
|
else return super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
return super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||||
|
if (KeyboardManager.hasHardwareKeyboard(this)) {
|
||||||
|
boolean handledKeyCode = keyboardManager.onKeyUp(keyCode, event);
|
||||||
|
if (handledKeyCode) return true;
|
||||||
|
else return super.onKeyUp(keyCode, event);
|
||||||
|
}
|
||||||
|
return super.onKeyUp(keyCode, event);
|
||||||
|
}
|
||||||
|
|
||||||
public void updateUnreadCounts(int neutCount, int posiCount) {
|
public void updateUnreadCounts(int neutCount, int posiCount) {
|
||||||
binding.mainUnreadCountNeutText.setText(Integer.toString(neutCount));
|
binding.mainUnreadCountNeutText.setText(Integer.toString(neutCount));
|
||||||
|
@ -325,4 +358,58 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
||||||
}
|
}
|
||||||
folderFeedList.setSearchQuery(q);
|
folderFeedList.setSearchQuery(q);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void openAllStories(boolean isAllStoriesSearch) {
|
||||||
|
Intent intent = new Intent(this, AllStoriesItemsList.class);
|
||||||
|
intent.putExtra(ItemsList.EXTRA_FEED_SET, FeedSet.allFeeds());
|
||||||
|
intent.putExtra(ItemsList.EXTRA_VISIBLE_SEARCH, isAllStoriesSearch);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void switchViewStateLeft() {
|
||||||
|
StateFilter currentState = folderFeedList.currentState;
|
||||||
|
if (currentState.equals(StateFilter.SAVED)) {
|
||||||
|
setAndNotifySelectorState(StateFilter.BEST, R.string.focused_stories);
|
||||||
|
} else if (currentState.equals(StateFilter.BEST)) {
|
||||||
|
setAndNotifySelectorState(StateFilter.SOME, R.string.unread_stories);
|
||||||
|
} else if (currentState.equals(StateFilter.SOME)) {
|
||||||
|
setAndNotifySelectorState(StateFilter.ALL, R.string.all_stories);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void switchViewStateRight() {
|
||||||
|
StateFilter currentState = folderFeedList.currentState;
|
||||||
|
if (currentState.equals(StateFilter.ALL)) {
|
||||||
|
setAndNotifySelectorState(StateFilter.SOME, R.string.unread_stories);
|
||||||
|
} else if (currentState.equals(StateFilter.SOME)) {
|
||||||
|
setAndNotifySelectorState(StateFilter.BEST, R.string.focused_stories);
|
||||||
|
} else if (currentState.equals(StateFilter.BEST)) {
|
||||||
|
setAndNotifySelectorState(StateFilter.SAVED, R.string.saved_stories);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAndNotifySelectorState(StateFilter state, @StringRes int notifyMsgRes) {
|
||||||
|
feedSelectorFragment.setState(state);
|
||||||
|
UIUtils.showSnackBar(binding.getRoot(), getString(notifyMsgRes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showFeedShortcuts() {
|
||||||
|
FeedsShortcutFragment newFragment = new FeedsShortcutFragment();
|
||||||
|
newFragment.show(getSupportFragmentManager(), FeedsShortcutFragment.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onKeyboardEvent(@NonNull KeyboardEvent event) {
|
||||||
|
if (event instanceof KeyboardEvent.AddFeed) {
|
||||||
|
onClickAddButton();
|
||||||
|
} else if (event instanceof KeyboardEvent.OpenAllStories) {
|
||||||
|
openAllStories(false);
|
||||||
|
} else if (event instanceof KeyboardEvent.SwitchViewLeft) {
|
||||||
|
switchViewStateLeft();
|
||||||
|
} else if (event instanceof KeyboardEvent.SwitchViewRight) {
|
||||||
|
switchViewStateRight();
|
||||||
|
} else if (event instanceof KeyboardEvent.Tutorial) {
|
||||||
|
showFeedShortcuts();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,15 +59,14 @@ public class MuteConfig extends FeedChooser implements MuteConfigAdapter.FeedSta
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == R.id.menu_mute_all) {
|
||||||
case R.id.menu_mute_all:
|
setFeedsState(true);
|
||||||
setFeedsState(true);
|
return true;
|
||||||
return true;
|
} else if (item.getItemId() == R.id.menu_mute_none) {
|
||||||
case R.id.menu_mute_none:
|
setFeedsState(false);
|
||||||
setFeedsState(false);
|
return true;
|
||||||
return true;
|
} else {
|
||||||
default:
|
return super.onOptionsItemSelected(item);
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.newsblur.activity
|
package com.newsblur.activity
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
|
@ -22,7 +23,11 @@ import com.newsblur.databinding.ActivityReadingBinding
|
||||||
import com.newsblur.di.IconLoader
|
import com.newsblur.di.IconLoader
|
||||||
import com.newsblur.domain.Story
|
import com.newsblur.domain.Story
|
||||||
import com.newsblur.fragment.ReadingItemFragment
|
import com.newsblur.fragment.ReadingItemFragment
|
||||||
|
import com.newsblur.fragment.ReadingItemFragment.Companion.VERTICAL_SCROLL_DISTANCE_DP
|
||||||
import com.newsblur.fragment.ReadingPagerFragment
|
import com.newsblur.fragment.ReadingPagerFragment
|
||||||
|
import com.newsblur.keyboard.KeyboardEvent
|
||||||
|
import com.newsblur.keyboard.KeyboardListener
|
||||||
|
import com.newsblur.keyboard.KeyboardManager
|
||||||
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_REBUILD
|
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_REBUILD
|
||||||
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_STATUS
|
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_STATUS
|
||||||
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_STORY
|
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_STORY
|
||||||
|
@ -42,7 +47,7 @@ import javax.inject.Inject
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
abstract class Reading : NbActivity(), OnPageChangeListener, ScrollChangeListener {
|
abstract class Reading : NbActivity(), OnPageChangeListener, ScrollChangeListener, KeyboardListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var feedUtils: FeedUtils
|
lateinit var feedUtils: FeedUtils
|
||||||
|
@ -84,6 +89,7 @@ abstract class Reading : NbActivity(), OnPageChangeListener, ScrollChangeListene
|
||||||
private var isMultiWindowModeHack = false
|
private var isMultiWindowModeHack = false
|
||||||
|
|
||||||
private val pageHistory = mutableListOf<Story>()
|
private val pageHistory = mutableListOf<Story>()
|
||||||
|
private val keyboardManager = KeyboardManager()
|
||||||
|
|
||||||
private lateinit var volumeKeyNavigation: VolumeKeyNavigation
|
private lateinit var volumeKeyNavigation: VolumeKeyNavigation
|
||||||
private lateinit var intelState: StateFilter
|
private lateinit var intelState: StateFilter
|
||||||
|
@ -136,7 +142,7 @@ abstract class Reading : NbActivity(), OnPageChangeListener, ScrollChangeListene
|
||||||
setupViews()
|
setupViews()
|
||||||
setupListeners()
|
setupListeners()
|
||||||
setupObservers()
|
setupObservers()
|
||||||
getActiveStoriesCursor(true)
|
getActiveStoriesCursor(this, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
@ -164,11 +170,13 @@ abstract class Reading : NbActivity(), OnPageChangeListener, ScrollChangeListene
|
||||||
// this is not strictly necessary, since our first refresh with the fs will swap in
|
// this is not strictly necessary, since our first refresh with the fs will swap in
|
||||||
// the correct session, but that can be delayed by sync backup, so we try here to
|
// the correct session, but that can be delayed by sync backup, so we try here to
|
||||||
// reduce UI lag, or in case somehow we got redisplayed in a zero-story state
|
// reduce UI lag, or in case somehow we got redisplayed in a zero-story state
|
||||||
feedUtils.prepareReadingSession(fs, false)
|
feedUtils.prepareReadingSession(this, fs, false)
|
||||||
|
keyboardManager.addListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
|
keyboardManager.removeListener()
|
||||||
if (isMultiWindowModeHack) {
|
if (isMultiWindowModeHack) {
|
||||||
isMultiWindowModeHack = false
|
isMultiWindowModeHack = false
|
||||||
} else {
|
} else {
|
||||||
|
@ -218,9 +226,10 @@ abstract class Reading : NbActivity(), OnPageChangeListener, ScrollChangeListene
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getActiveStoriesCursor(finishOnInvalidFs: Boolean = false) {
|
private fun getActiveStoriesCursor(context: Context, finishOnInvalidFs: Boolean = false) {
|
||||||
fs?.let {
|
fs?.let {
|
||||||
storiesViewModel.getActiveStories(it)
|
val cursorFilters = CursorFilters(context, it)
|
||||||
|
storiesViewModel.getActiveStories(it, cursorFilters)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
if (finishOnInvalidFs) {
|
if (finishOnInvalidFs) {
|
||||||
Log.e(this.javaClass.name, "can't create activity, no feedset ready")
|
Log.e(this.javaClass.name, "can't create activity, no feedset ready")
|
||||||
|
@ -385,7 +394,7 @@ abstract class Reading : NbActivity(), OnPageChangeListener, ScrollChangeListene
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (updateType and UPDATE_STORY != 0) {
|
if (updateType and UPDATE_STORY != 0) {
|
||||||
getActiveStoriesCursor()
|
getActiveStoriesCursor(this)
|
||||||
updateOverlayNav()
|
updateOverlayNav()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -737,6 +746,10 @@ abstract class Reading : NbActivity(), OnPageChangeListener, ScrollChangeListene
|
||||||
return if (isVolumeKeyNavigationEvent(keyCode)) {
|
return if (isVolumeKeyNavigationEvent(keyCode)) {
|
||||||
processVolumeKeyNavigationEvent(keyCode)
|
processVolumeKeyNavigationEvent(keyCode)
|
||||||
true
|
true
|
||||||
|
} else if (KeyboardManager.hasHardwareKeyboard(this)) {
|
||||||
|
val isKnownKeyCode = keyboardManager.isKnownKeyCode(keyCode)
|
||||||
|
if (isKnownKeyCode) true
|
||||||
|
else super.onKeyDown(keyCode, event)
|
||||||
} else {
|
} else {
|
||||||
super.onKeyDown(keyCode, event)
|
super.onKeyDown(keyCode, event)
|
||||||
}
|
}
|
||||||
|
@ -748,24 +761,32 @@ abstract class Reading : NbActivity(), OnPageChangeListener, ScrollChangeListene
|
||||||
private fun processVolumeKeyNavigationEvent(keyCode: Int) {
|
private fun processVolumeKeyNavigationEvent(keyCode: Int) {
|
||||||
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && volumeKeyNavigation == VolumeKeyNavigation.DOWN_NEXT ||
|
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && volumeKeyNavigation == VolumeKeyNavigation.DOWN_NEXT ||
|
||||||
keyCode == KeyEvent.KEYCODE_VOLUME_UP && volumeKeyNavigation == VolumeKeyNavigation.UP_NEXT) {
|
keyCode == KeyEvent.KEYCODE_VOLUME_UP && volumeKeyNavigation == VolumeKeyNavigation.UP_NEXT) {
|
||||||
if (pager == null) return
|
nextStory()
|
||||||
val nextPosition = pager!!.currentItem + 1
|
|
||||||
if (nextPosition < readingAdapter!!.count) {
|
|
||||||
try {
|
|
||||||
pager!!.currentItem = nextPosition
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// Just in case cursor changes.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (pager == null) return
|
previousStory()
|
||||||
val nextPosition = pager!!.currentItem - 1
|
}
|
||||||
if (nextPosition >= 0) {
|
}
|
||||||
try {
|
|
||||||
pager!!.currentItem = nextPosition
|
private fun nextStory() {
|
||||||
} catch (e: Exception) {
|
if (pager == null) return
|
||||||
// Just in case cursor changes.
|
val nextPosition = pager!!.currentItem + 1
|
||||||
}
|
if (nextPosition < readingAdapter!!.count) {
|
||||||
|
try {
|
||||||
|
pager!!.currentItem = nextPosition
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Just in case cursor changes.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun previousStory() {
|
||||||
|
if (pager == null) return
|
||||||
|
val nextPosition = pager!!.currentItem - 1
|
||||||
|
if (nextPosition >= 0) {
|
||||||
|
try {
|
||||||
|
pager!!.currentItem = nextPosition
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Just in case cursor changes.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -774,6 +795,10 @@ abstract class Reading : NbActivity(), OnPageChangeListener, ScrollChangeListene
|
||||||
// Required to prevent the default sound playing when the volume key is pressed
|
// Required to prevent the default sound playing when the volume key is pressed
|
||||||
return if (isVolumeKeyNavigationEvent(keyCode)) {
|
return if (isVolumeKeyNavigationEvent(keyCode)) {
|
||||||
true
|
true
|
||||||
|
} else if (KeyboardManager.hasHardwareKeyboard(this)) {
|
||||||
|
val handledKeyCode = keyboardManager.onKeyUp(keyCode, event)
|
||||||
|
if (handledKeyCode) true
|
||||||
|
else super.onKeyUp(keyCode, event)
|
||||||
} else {
|
} else {
|
||||||
super.onKeyUp(keyCode, event)
|
super.onKeyUp(keyCode, event)
|
||||||
}
|
}
|
||||||
|
@ -797,6 +822,27 @@ abstract class Reading : NbActivity(), OnPageChangeListener, ScrollChangeListene
|
||||||
if (isActive) feedUtils.markStoryAsRead(story, this@Reading)
|
if (isActive) feedUtils.markStoryAsRead(story, this@Reading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onKeyboardEvent(event: KeyboardEvent) {
|
||||||
|
when (event) {
|
||||||
|
KeyboardEvent.NextStory -> nextStory()
|
||||||
|
KeyboardEvent.PreviousStory -> previousStory()
|
||||||
|
KeyboardEvent.NextUnreadStory -> nextUnread()
|
||||||
|
KeyboardEvent.OpenInBrowser -> readingFragment?.openBrowser()
|
||||||
|
KeyboardEvent.OpenStoryTrainer -> readingFragment?.openStoryTrainer()
|
||||||
|
KeyboardEvent.SaveUnsaveStory -> readingFragment?.switchStorySavedState(true)
|
||||||
|
KeyboardEvent.ScrollToComments -> readingFragment?.scrollToComments()
|
||||||
|
KeyboardEvent.ShareStory -> readingFragment?.openShareDialog()
|
||||||
|
KeyboardEvent.ToggleReadUnread -> readingFragment?.switchMarkStoryReadState(true)
|
||||||
|
KeyboardEvent.ToggleTextView -> readingFragment?.switchSelectedViewMode()
|
||||||
|
KeyboardEvent.Tutorial -> readingFragment?.showStoryShortcuts()
|
||||||
|
KeyboardEvent.PageDown ->
|
||||||
|
readingFragment?.scrollVerticallyBy(UIUtils.dp2px(this, VERTICAL_SCROLL_DISTANCE_DP))
|
||||||
|
KeyboardEvent.PageUp ->
|
||||||
|
readingFragment?.scrollVerticallyBy(UIUtils.dp2px(this, -VERTICAL_SCROLL_DISTANCE_DP))
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val EXTRA_FEEDSET = "feed_set"
|
const val EXTRA_FEEDSET = "feed_set"
|
||||||
const val EXTRA_STORY_HASH = "story_hash"
|
const val EXTRA_STORY_HASH = "story_hash"
|
||||||
|
|
|
@ -62,15 +62,14 @@ public class WidgetConfig extends FeedChooser {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == R.id.menu_select_all) {
|
||||||
case R.id.menu_select_all:
|
selectAllFeeds();
|
||||||
selectAllFeeds();
|
return true;
|
||||||
return true;
|
} else if (item.getItemId() == R.id.menu_select_none) {
|
||||||
case R.id.menu_select_none:
|
replaceWidgetFeedIds(Collections.emptySet());
|
||||||
replaceWidgetFeedIds(Collections.emptySet());
|
return true;
|
||||||
return true;
|
} else {
|
||||||
default:
|
return super.onOptionsItemSelected(item);
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,13 +24,12 @@ import com.newsblur.domain.UserProfile;
|
||||||
import com.newsblur.network.domain.CommentResponse;
|
import com.newsblur.network.domain.CommentResponse;
|
||||||
import com.newsblur.network.domain.StoriesResponse;
|
import com.newsblur.network.domain.StoriesResponse;
|
||||||
import com.newsblur.util.AppConstants;
|
import com.newsblur.util.AppConstants;
|
||||||
|
import com.newsblur.util.CursorFilters;
|
||||||
import com.newsblur.util.FeedSet;
|
import com.newsblur.util.FeedSet;
|
||||||
import com.newsblur.util.PrefsUtils;
|
|
||||||
import com.newsblur.util.ReadingAction;
|
import com.newsblur.util.ReadingAction;
|
||||||
import com.newsblur.util.ReadFilter;
|
import com.newsblur.util.ReadFilter;
|
||||||
import com.newsblur.util.StateFilter;
|
import com.newsblur.util.StateFilter;
|
||||||
import com.newsblur.util.StoryOrder;
|
import com.newsblur.util.StoryOrder;
|
||||||
import com.newsblur.util.UIUtils;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -56,14 +55,12 @@ public class BlurDatabaseHelper {
|
||||||
// manual synchro isn't needed if you only use one DBHelper, but at present the app uses several
|
// manual synchro isn't needed if you only use one DBHelper, but at present the app uses several
|
||||||
public final static Object RW_MUTEX = new Object();
|
public final static Object RW_MUTEX = new Object();
|
||||||
|
|
||||||
private Context context;
|
|
||||||
private final BlurDatabase dbWrapper;
|
private final BlurDatabase dbWrapper;
|
||||||
private SQLiteDatabase dbRO;
|
private final SQLiteDatabase dbRO;
|
||||||
private SQLiteDatabase dbRW;
|
private final SQLiteDatabase dbRW;
|
||||||
|
|
||||||
public BlurDatabaseHelper(Context context) {
|
public BlurDatabaseHelper(Context context) {
|
||||||
com.newsblur.util.Log.d(this.getClass().getName(), "new DB conn requested");
|
com.newsblur.util.Log.d(this.getClass().getName(), "new DB conn requested");
|
||||||
this.context = context;
|
|
||||||
synchronized (RW_MUTEX) {
|
synchronized (RW_MUTEX) {
|
||||||
dbWrapper = new BlurDatabase(context);
|
dbWrapper = new BlurDatabase(context);
|
||||||
dbRO = dbWrapper.getRO();
|
dbRO = dbWrapper.getRO();
|
||||||
|
@ -328,8 +325,7 @@ public class BlurDatabaseHelper {
|
||||||
return urls;
|
return urls;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insertStories(StoriesResponse apiResponse, boolean forImmediateReading) {
|
public void insertStories(StoriesResponse apiResponse, StateFilter stateFilter, boolean forImmediateReading) {
|
||||||
StateFilter intelState = PrefsUtils.getStateFilter(context);
|
|
||||||
synchronized (RW_MUTEX) {
|
synchronized (RW_MUTEX) {
|
||||||
// do not attempt to use beginTransactionNonExclusive() to reduce lock time for this very heavy set
|
// do not attempt to use beginTransactionNonExclusive() to reduce lock time for this very heavy set
|
||||||
// of calls. most versions of Android incorrectly implement the underlying SQLite calls and will
|
// of calls. most versions of Android incorrectly implement the underlying SQLite calls and will
|
||||||
|
@ -369,7 +365,7 @@ public class BlurDatabaseHelper {
|
||||||
}
|
}
|
||||||
insertSingleStoryExtSync(story);
|
insertSingleStoryExtSync(story);
|
||||||
// if the story is being fetched for the immediate session, also add the hash to the session table
|
// if the story is being fetched for the immediate session, also add the hash to the session table
|
||||||
if (forImmediateReading && story.isStoryVisibileInState(intelState)) {
|
if (forImmediateReading && story.isStoryVisibleInState(stateFilter)) {
|
||||||
ContentValues sessionHashValues = new ContentValues();
|
ContentValues sessionHashValues = new ContentValues();
|
||||||
sessionHashValues.put(DatabaseConstants.READING_SESSION_STORY_HASH, story.storyHash);
|
sessionHashValues.put(DatabaseConstants.READING_SESSION_STORY_HASH, story.storyHash);
|
||||||
dbRW.insert(DatabaseConstants.READING_SESSION_TABLE, null, sessionHashValues);
|
dbRW.insert(DatabaseConstants.READING_SESSION_TABLE, null, sessionHashValues);
|
||||||
|
@ -481,7 +477,7 @@ public class BlurDatabaseHelper {
|
||||||
* to reflect a social action, but that the new copy is missing some fields. Attempt to merge the
|
* to reflect a social action, but that the new copy is missing some fields. Attempt to merge the
|
||||||
* new story with the old one.
|
* new story with the old one.
|
||||||
*/
|
*/
|
||||||
public void updateStory(StoriesResponse apiResponse, boolean forImmediateReading) {
|
public void updateStory(StoriesResponse apiResponse, StateFilter stateFilter, boolean forImmediateReading) {
|
||||||
if (apiResponse.story == null) {
|
if (apiResponse.story == null) {
|
||||||
com.newsblur.util.Log.e(this, "updateStory called on response with missing single story");
|
com.newsblur.util.Log.e(this, "updateStory called on response with missing single story");
|
||||||
return;
|
return;
|
||||||
|
@ -500,7 +496,7 @@ public class BlurDatabaseHelper {
|
||||||
apiResponse.story.starredTimestamp = oldStory.starredTimestamp;
|
apiResponse.story.starredTimestamp = oldStory.starredTimestamp;
|
||||||
apiResponse.story.read = oldStory.read;
|
apiResponse.story.read = oldStory.read;
|
||||||
}
|
}
|
||||||
insertStories(apiResponse, forImmediateReading);
|
insertStories(apiResponse, stateFilter, forImmediateReading);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -754,8 +750,8 @@ public class BlurDatabaseHelper {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(DatabaseConstants.STORY_READ, true);
|
values.put(DatabaseConstants.STORY_READ, true);
|
||||||
String rangeSelection = null;
|
String rangeSelection = null;
|
||||||
if (olderThan != null) rangeSelection = DatabaseConstants.STORY_TIMESTAMP + " <= " + olderThan.toString();
|
if (olderThan != null) rangeSelection = DatabaseConstants.STORY_TIMESTAMP + " <= " + olderThan;
|
||||||
if (newerThan != null) rangeSelection = DatabaseConstants.STORY_TIMESTAMP + " >= " + newerThan.toString();
|
if (newerThan != null) rangeSelection = DatabaseConstants.STORY_TIMESTAMP + " >= " + newerThan;
|
||||||
StringBuilder feedSelection = null;
|
StringBuilder feedSelection = null;
|
||||||
if (fs.isAllNormal()) {
|
if (fs.isAllNormal()) {
|
||||||
// a null selection is fine for all stories
|
// a null selection is fine for all stories
|
||||||
|
@ -982,7 +978,7 @@ public class BlurDatabaseHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStoryShared(String hash, boolean shared) {
|
public void setStoryShared(String hash, @Nullable String currentUserId, boolean shared) {
|
||||||
// get a fresh copy of the story from the DB so we can append to the shared ID set
|
// get a fresh copy of the story from the DB so we can append to the shared ID set
|
||||||
Cursor c = dbRO.query(DatabaseConstants.STORY_TABLE,
|
Cursor c = dbRO.query(DatabaseConstants.STORY_TABLE,
|
||||||
new String[]{DatabaseConstants.STORY_SHARED_USER_IDS},
|
new String[]{DatabaseConstants.STORY_SHARED_USER_IDS},
|
||||||
|
@ -998,15 +994,13 @@ public class BlurDatabaseHelper {
|
||||||
String[] sharedUserIds = TextUtils.split(c.getString(c.getColumnIndex(DatabaseConstants.STORY_SHARED_USER_IDS)), ",");
|
String[] sharedUserIds = TextUtils.split(c.getString(c.getColumnIndex(DatabaseConstants.STORY_SHARED_USER_IDS)), ",");
|
||||||
closeQuietly(c);
|
closeQuietly(c);
|
||||||
|
|
||||||
// the id to append to or remove from the shared list (the current user)
|
|
||||||
String currentUser = PrefsUtils.getUserId(context);
|
|
||||||
|
|
||||||
// append to set and update DB
|
// append to set and update DB
|
||||||
Set<String> newIds = new HashSet<String>(Arrays.asList(sharedUserIds));
|
Set<String> newIds = new HashSet<String>(Arrays.asList(sharedUserIds));
|
||||||
|
// the id to append to or remove from the shared list (the current user)
|
||||||
if (shared) {
|
if (shared) {
|
||||||
newIds.add(currentUser);
|
newIds.add(currentUserId);
|
||||||
} else {
|
} else {
|
||||||
newIds.remove(currentUser);
|
newIds.remove(currentUserId);
|
||||||
}
|
}
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(DatabaseConstants.STORY_SHARED_USER_IDS, TextUtils.join(",", newIds));
|
values.put(DatabaseConstants.STORY_SHARED_USER_IDS, TextUtils.join(",", newIds));
|
||||||
|
@ -1146,16 +1140,16 @@ public class BlurDatabaseHelper {
|
||||||
return rawQuery(q.toString(), null, cancellationSignal);
|
return rawQuery(q.toString(), null, cancellationSignal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cursor getActiveStoriesCursor(FeedSet fs, CancellationSignal cancellationSignal) {
|
public Cursor getActiveStoriesCursor(FeedSet fs, CursorFilters cursorFilters, CancellationSignal cancellationSignal) {
|
||||||
final StoryOrder order = PrefsUtils.getStoryOrder(context, fs);
|
|
||||||
// get the stories for this FS
|
// get the stories for this FS
|
||||||
Cursor result = getActiveStoriesCursorNoPrep(fs, order, cancellationSignal);
|
Cursor result = getActiveStoriesCursorNoPrep(fs, cursorFilters.getStoryOrder(), cancellationSignal);
|
||||||
// if the result is blank, try to prime the session table with existing stories, in case we
|
// if the result is blank, try to prime the session table with existing stories, in case we
|
||||||
// are offline, but if a session is started, just use what was there so offsets don't change.
|
// are offline, but if a session is started, just use what was there so offsets don't change.
|
||||||
if (result.getCount() < 1) {
|
if (result.getCount() < 1) {
|
||||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "priming reading session");
|
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "priming reading session");
|
||||||
prepareReadingSession(fs);
|
prepareReadingSession(fs, cursorFilters.getStateFilter(), cursorFilters.getReadFilter());
|
||||||
result = getActiveStoriesCursorNoPrep(fs, order, cancellationSignal);
|
|
||||||
|
result = getActiveStoriesCursorNoPrep(fs, cursorFilters.getStoryOrder(), cancellationSignal);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1183,18 +1177,12 @@ public class BlurDatabaseHelper {
|
||||||
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.READING_SESSION_TABLE, null, null);}
|
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.READING_SESSION_TABLE, null, null);}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void prepareReadingSession(FeedSet fs) {
|
|
||||||
ReadFilter readFilter = PrefsUtils.getReadFilter(context, fs);
|
|
||||||
StateFilter stateFilter = PrefsUtils.getStateFilter(context);
|
|
||||||
prepareReadingSession(fs, stateFilter, readFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populates the reading session table with hashes of already-fetched stories that meet the
|
* Populates the reading session table with hashes of already-fetched stories that meet the
|
||||||
* criteria for the given FeedSet and filters; these hashes will be supplemented by hashes
|
* criteria for the given FeedSet and filters; these hashes will be supplemented by hashes
|
||||||
* fetched via the API and used to actually select story data when rendering story lists.
|
* fetched via the API and used to actually select story data when rendering story lists.
|
||||||
*/
|
*/
|
||||||
private void prepareReadingSession(FeedSet fs, StateFilter stateFilter, ReadFilter readFilter) {
|
public void prepareReadingSession(FeedSet fs, StateFilter stateFilter, ReadFilter readFilter) {
|
||||||
// a selection filter that will be used to pull active story hashes from the stories table into the reading session table
|
// a selection filter that will be used to pull active story hashes from the stories table into the reading session table
|
||||||
StringBuilder sel = new StringBuilder();
|
StringBuilder sel = new StringBuilder();
|
||||||
// any selection args that need to be used within the inner select statement
|
// any selection args that need to be used within the inner select statement
|
||||||
|
@ -1366,8 +1354,7 @@ public class BlurDatabaseHelper {
|
||||||
* will show up in the UI with reduced functionality until the server gets back to us with
|
* will show up in the UI with reduced functionality until the server gets back to us with
|
||||||
* an ID at which time the placeholder will be removed.
|
* an ID at which time the placeholder will be removed.
|
||||||
*/
|
*/
|
||||||
public void insertCommentPlaceholder(String storyId, String feedId, String commentText) {
|
public void insertCommentPlaceholder(String storyId, @Nullable String userId, String commentText) {
|
||||||
String userId = PrefsUtils.getUserId(context);
|
|
||||||
Comment comment = new Comment();
|
Comment comment = new Comment();
|
||||||
comment.isPlaceholder = true;
|
comment.isPlaceholder = true;
|
||||||
comment.id = Comment.PLACEHOLDER_COMMENT_ID + storyId + userId;
|
comment.id = Comment.PLACEHOLDER_COMMENT_ID + storyId + userId;
|
||||||
|
@ -1399,19 +1386,18 @@ public class BlurDatabaseHelper {
|
||||||
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.REPLY_TABLE, DatabaseConstants.REPLY_ID + " = ?", new String[]{replyId});}
|
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.REPLY_TABLE, DatabaseConstants.REPLY_ID + " = ?", new String[]{replyId});}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearSelfComments(String storyId) {
|
public void clearSelfComments(String storyId, @Nullable String userId) {
|
||||||
String userId = PrefsUtils.getUserId(context);
|
|
||||||
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.COMMENT_TABLE,
|
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.COMMENT_TABLE,
|
||||||
DatabaseConstants.COMMENT_STORYID + " = ? AND " + DatabaseConstants.COMMENT_USERID + " = ?",
|
DatabaseConstants.COMMENT_STORYID + " = ? AND " + DatabaseConstants.COMMENT_USERID + " = ?",
|
||||||
new String[]{storyId, userId});}
|
new String[]{storyId, userId});}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCommentLiked(String storyId, String userId, String feedId, boolean liked) {
|
public void setCommentLiked(String storyId, String commentUserId, @Nullable String currentUserId, boolean liked) {
|
||||||
// get a fresh copy of the story from the DB so we can append to the shared ID set
|
// get a fresh copy of the story from the DB so we can append to the shared ID set
|
||||||
Cursor c = dbRO.query(DatabaseConstants.COMMENT_TABLE,
|
Cursor c = dbRO.query(DatabaseConstants.COMMENT_TABLE,
|
||||||
null,
|
null,
|
||||||
DatabaseConstants.COMMENT_STORYID + " = ? AND " + DatabaseConstants.COMMENT_USERID + " = ?",
|
DatabaseConstants.COMMENT_STORYID + " = ? AND " + DatabaseConstants.COMMENT_USERID + " = ?",
|
||||||
new String[]{storyId, userId},
|
new String[]{storyId, commentUserId},
|
||||||
null, null, null);
|
null, null, null);
|
||||||
if ((c == null)||(c.getCount() < 1)) {
|
if ((c == null)||(c.getCount() < 1)) {
|
||||||
Log.w(this.getClass().getName(), "comment removed before finishing mark-liked");
|
Log.w(this.getClass().getName(), "comment removed before finishing mark-liked");
|
||||||
|
@ -1422,15 +1408,13 @@ public class BlurDatabaseHelper {
|
||||||
Comment comment = Comment.fromCursor(c);
|
Comment comment = Comment.fromCursor(c);
|
||||||
closeQuietly(c);
|
closeQuietly(c);
|
||||||
|
|
||||||
// the new id to append/remove from the liking list (the current user)
|
|
||||||
String currentUser = PrefsUtils.getUserId(context);
|
|
||||||
|
|
||||||
// append to set and update DB
|
// append to set and update DB
|
||||||
Set<String> newIds = new HashSet<String>(Arrays.asList(comment.likingUsers));
|
Set<String> newIds = new HashSet<String>(Arrays.asList(comment.likingUsers));
|
||||||
|
// the new id to append/remove from the liking list (the current user)
|
||||||
if (liked) {
|
if (liked) {
|
||||||
newIds.add(currentUser);
|
newIds.add(currentUserId);
|
||||||
} else {
|
} else {
|
||||||
newIds.remove(currentUser);
|
newIds.remove(currentUserId);
|
||||||
}
|
}
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(DatabaseConstants.COMMENT_LIKING_USERS, TextUtils.join(",", newIds));
|
values.put(DatabaseConstants.COMMENT_LIKING_USERS, TextUtils.join(",", newIds));
|
||||||
|
@ -1458,7 +1442,7 @@ public class BlurDatabaseHelper {
|
||||||
return replies;
|
return replies;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insertReplyPlaceholder(String storyId, String feedId, String commentUserId, String replyText) {
|
public void insertReplyPlaceholder(String storyId, @Nullable String userId, String commentUserId, String replyText) {
|
||||||
// get a fresh copy of the comment so we can discover the ID
|
// get a fresh copy of the comment so we can discover the ID
|
||||||
Cursor c = dbRO.query(DatabaseConstants.COMMENT_TABLE,
|
Cursor c = dbRO.query(DatabaseConstants.COMMENT_TABLE,
|
||||||
null,
|
null,
|
||||||
|
@ -1477,7 +1461,7 @@ public class BlurDatabaseHelper {
|
||||||
Reply reply = new Reply();
|
Reply reply = new Reply();
|
||||||
reply.commentId = comment.id;
|
reply.commentId = comment.id;
|
||||||
reply.text = replyText;
|
reply.text = replyText;
|
||||||
reply.userId = PrefsUtils.getUserId(context);
|
reply.userId = userId;
|
||||||
reply.date = new Date();
|
reply.date = new Date();
|
||||||
reply.id = Reply.PLACEHOLDER_COMMENT_ID + storyId + comment.id + reply.userId;
|
reply.id = Reply.PLACEHOLDER_COMMENT_ID + storyId + comment.id + reply.userId;
|
||||||
synchronized (RW_MUTEX) {dbRW.insertWithOnConflict(DatabaseConstants.REPLY_TABLE, null, reply.getValues(), SQLiteDatabase.CONFLICT_REPLACE);}
|
synchronized (RW_MUTEX) {dbRW.insertWithOnConflict(DatabaseConstants.REPLY_TABLE, null, reply.getValues(), SQLiteDatabase.CONFLICT_REPLACE);}
|
||||||
|
@ -1582,11 +1566,8 @@ public class BlurDatabaseHelper {
|
||||||
|
|
||||||
public static void closeQuietly(Cursor c) {
|
public static void closeQuietly(Cursor c) {
|
||||||
if (c == null) return;
|
if (c == null) return;
|
||||||
try {c.close();} catch (Exception e) {;}
|
try {c.close();} catch (Exception e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendSyncUpdate(int updateType) {
|
|
||||||
UIUtils.syncUpdateStatus(context, updateType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String conjoinSelections(CharSequence... args) {
|
private static String conjoinSelections(CharSequence... args) {
|
||||||
|
|
|
@ -413,52 +413,42 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMenuItemClick(MenuItem item) {
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == R.id.menu_mark_story_as_read) {
|
||||||
case R.id.menu_mark_story_as_read:
|
|
||||||
feedUtils.markStoryAsRead(story, context);
|
feedUtils.markStoryAsRead(story, context);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_mark_story_as_unread) {
|
||||||
case R.id.menu_mark_story_as_unread:
|
|
||||||
feedUtils.markStoryUnread(story, context);
|
feedUtils.markStoryUnread(story, context);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_mark_older_stories_as_read) {
|
||||||
case R.id.menu_mark_older_stories_as_read:
|
|
||||||
feedUtils.markRead(context, fs, story.timestamp, null, R.array.mark_older_read_options);
|
feedUtils.markRead(context, fs, story.timestamp, null, R.array.mark_older_read_options);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_mark_newer_stories_as_read) {
|
||||||
case R.id.menu_mark_newer_stories_as_read:
|
|
||||||
feedUtils.markRead(context, fs, null, story.timestamp, R.array.mark_newer_read_options);
|
feedUtils.markRead(context, fs, null, story.timestamp, R.array.mark_newer_read_options);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_send_story) {
|
||||||
case R.id.menu_send_story:
|
|
||||||
feedUtils.sendStoryUrl(story, context);
|
feedUtils.sendStoryUrl(story, context);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_send_story_full) {
|
||||||
case R.id.menu_send_story_full:
|
|
||||||
feedUtils.sendStoryFull(story, context);
|
feedUtils.sendStoryFull(story, context);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_save_story) {
|
||||||
case R.id.menu_save_story:
|
|
||||||
//TODO get folder name
|
//TODO get folder name
|
||||||
feedUtils.setStorySaved(story, true, context, null);
|
feedUtils.setStorySaved(story, true, context, null);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_unsave_story) {
|
||||||
case R.id.menu_unsave_story:
|
|
||||||
feedUtils.setStorySaved(story, false, context, null);
|
feedUtils.setStorySaved(story, false, context, null);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_intel) {
|
||||||
case R.id.menu_intel:
|
|
||||||
if (story.feedId.equals("0")) return true; // cannot train on feedless stories
|
if (story.feedId.equals("0")) return true; // cannot train on feedless stories
|
||||||
StoryIntelTrainerFragment intelFrag = StoryIntelTrainerFragment.newInstance(story, fs);
|
StoryIntelTrainerFragment intelFrag = StoryIntelTrainerFragment.newInstance(story, fs);
|
||||||
intelFrag.show(context.getSupportFragmentManager(), StoryIntelTrainerFragment.class.getName());
|
intelFrag.show(context.getSupportFragmentManager(), StoryIntelTrainerFragment.class.getName());
|
||||||
return true;
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_go_to_feed) {
|
||||||
case R.id.menu_go_to_feed:
|
|
||||||
FeedSet fs = FeedSet.singleFeed(story.feedId);
|
FeedSet fs = FeedSet.singleFeed(story.feedId);
|
||||||
FeedItemsList.startActivity(context, fs,
|
FeedItemsList.startActivity(context, fs,
|
||||||
feedUtils.getFeed(story.feedId), null, null);
|
feedUtils.getFeed(story.feedId), null, null);
|
||||||
return true;
|
return true;
|
||||||
default:
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -303,7 +303,7 @@ open class ItemListContextMenuDelegateImpl(
|
||||||
|
|
||||||
private fun restartReadingSession(fragment: ItemSetFragment, fs: FeedSet) {
|
private fun restartReadingSession(fragment: ItemSetFragment, fs: FeedSet) {
|
||||||
NBSyncService.resetFetchState(fs)
|
NBSyncService.resetFetchState(fs)
|
||||||
feedUtils.prepareReadingSession(fs, true)
|
feedUtils.prepareReadingSession(activity, fs, true)
|
||||||
triggerSync(activity)
|
triggerSync(activity)
|
||||||
fragment.resetEmptyState()
|
fragment.resetEmptyState()
|
||||||
fragment.hasUpdated()
|
fragment.hasUpdated()
|
||||||
|
|
|
@ -10,10 +10,8 @@ import androidx.fragment.app.DialogFragment
|
||||||
import com.newsblur.R
|
import com.newsblur.R
|
||||||
import com.newsblur.activity.*
|
import com.newsblur.activity.*
|
||||||
import com.newsblur.database.BlurDatabaseHelper
|
import com.newsblur.database.BlurDatabaseHelper
|
||||||
import com.newsblur.fragment.FolderListFragment
|
import com.newsblur.fragment.*
|
||||||
import com.newsblur.fragment.LoginAsDialogFragment
|
import com.newsblur.keyboard.KeyboardManager
|
||||||
import com.newsblur.fragment.LogoutDialogFragment
|
|
||||||
import com.newsblur.fragment.NewslettersFragment
|
|
||||||
import com.newsblur.service.NBSyncService
|
import com.newsblur.service.NBSyncService
|
||||||
import com.newsblur.util.ListTextSize
|
import com.newsblur.util.ListTextSize
|
||||||
import com.newsblur.util.ListTextSize.Companion.fromSize
|
import com.newsblur.util.ListTextSize.Companion.fromSize
|
||||||
|
@ -44,6 +42,10 @@ class MainContextMenuDelegateImpl(
|
||||||
menu.findItem(R.id.menu_loginas).isVisible = true
|
menu.findItem(R.id.menu_loginas).isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (KeyboardManager.hasHardwareKeyboard(activity)) {
|
||||||
|
menu.findItem(R.id.menu_shortcuts).isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
when (PrefsUtils.getSelectedTheme(activity)) {
|
when (PrefsUtils.getSelectedTheme(activity)) {
|
||||||
ThemeValue.LIGHT -> menu.findItem(R.id.menu_theme_light).isChecked = true
|
ThemeValue.LIGHT -> menu.findItem(R.id.menu_theme_light).isChecked = true
|
||||||
ThemeValue.DARK -> menu.findItem(R.id.menu_theme_dark).isChecked = true
|
ThemeValue.DARK -> menu.findItem(R.id.menu_theme_dark).isChecked = true
|
||||||
|
@ -184,10 +186,15 @@ class MainContextMenuDelegateImpl(
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.menu_newsletters -> {
|
R.id.menu_newsletters -> {
|
||||||
val newFragment: DialogFragment = NewslettersFragment()
|
val newFragment = NewslettersFragment()
|
||||||
newFragment.show(activity.supportFragmentManager, NewslettersFragment::class.java.name)
|
newFragment.show(activity.supportFragmentManager, NewslettersFragment::class.java.name)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.menu_shortcuts -> {
|
||||||
|
val newFragment = FeedsShortcutFragment()
|
||||||
|
newFragment.show(activity.supportFragmentManager, FeedsShortcutFragment::class.java.name)
|
||||||
|
true
|
||||||
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -238,7 +238,7 @@ public class Story implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isStoryVisibileInState(StateFilter state) {
|
public boolean isStoryVisibleInState(StateFilter state) {
|
||||||
int score = intelligence.calcTotalIntel();
|
int score = intelligence.calcTotalIntel();
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case ALL:
|
case ALL:
|
||||||
|
@ -252,7 +252,7 @@ public class Story implements Serializable {
|
||||||
case NEG:
|
case NEG:
|
||||||
return (score < 0);
|
return (score < 0);
|
||||||
case SAVED:
|
case SAVED:
|
||||||
return (starred == true);
|
return (starred);
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,10 +24,9 @@ import com.newsblur.fragment.AddFeedFragment.AddFeedAdapter.FolderViewHolder
|
||||||
import com.newsblur.network.APIManager
|
import com.newsblur.network.APIManager
|
||||||
import com.newsblur.service.NBSyncService
|
import com.newsblur.service.NBSyncService
|
||||||
import com.newsblur.util.AppConstants
|
import com.newsblur.util.AppConstants
|
||||||
import com.newsblur.util.UIUtils
|
|
||||||
import com.newsblur.util.executeAsyncTask
|
import com.newsblur.util.executeAsyncTask
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import java.util.*
|
import java.util.Collections
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
|
@ -88,14 +87,14 @@ class AddFeedFragment : DialogFragment() {
|
||||||
binding.inputFolderName.text.clear()
|
binding.inputFolderName.text.clear()
|
||||||
addFeed(activity, apiManager, folderName)
|
addFeed(activity, apiManager, folderName)
|
||||||
} else {
|
} else {
|
||||||
UIUtils.safeToast(activity, R.string.add_folder_error, Toast.LENGTH_SHORT)
|
Toast.makeText(activity, R.string.add_folder_error, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addFeed(activity: Activity, apiManager: APIManager, folderName: String?) {
|
private fun addFeed(activity: Activity, apiManager: APIManager, folderName: String?) {
|
||||||
binding.textSyncStatus.visibility = View.VISIBLE
|
binding.containerSyncStatus.visibility = View.VISIBLE
|
||||||
lifecycleScope.executeAsyncTask(
|
lifecycleScope.executeAsyncTask(
|
||||||
doInBackground = {
|
doInBackground = {
|
||||||
(activity as AddFeedProgressListener).addFeedStarted()
|
(activity as AddFeedProgressListener).addFeedStarted()
|
||||||
|
@ -103,7 +102,7 @@ class AddFeedFragment : DialogFragment() {
|
||||||
apiManager.addFeed(feedUrl, folderName)
|
apiManager.addFeed(feedUrl, folderName)
|
||||||
},
|
},
|
||||||
onPostExecute = {
|
onPostExecute = {
|
||||||
binding.textSyncStatus.visibility = View.GONE
|
binding.containerSyncStatus.visibility = View.GONE
|
||||||
val intent = Intent(activity, Main::class.java)
|
val intent = Intent(activity, Main::class.java)
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
|
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||||
if (!it.isError) {
|
if (!it.isError) {
|
||||||
|
@ -111,7 +110,7 @@ class AddFeedFragment : DialogFragment() {
|
||||||
NBSyncService.forceFeedsFolders()
|
NBSyncService.forceFeedsFolders()
|
||||||
intent.putExtra(Main.EXTRA_FORCE_SHOW_FEED_ID, it.feed.feedId)
|
intent.putExtra(Main.EXTRA_FORCE_SHOW_FEED_ID, it.feed.feedId)
|
||||||
} else {
|
} else {
|
||||||
UIUtils.safeToast(activity, R.string.add_feed_error, Toast.LENGTH_SHORT)
|
Toast.makeText(activity, R.string.add_feed_error, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
activity.startActivity(intent)
|
activity.startActivity(intent)
|
||||||
activity.finish()
|
activity.finish()
|
||||||
|
@ -119,8 +118,7 @@ class AddFeedFragment : DialogFragment() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AddFeedAdapter
|
private class AddFeedAdapter(private val listener: OnFolderClickListener) : RecyclerView.Adapter<FolderViewHolder>() {
|
||||||
constructor(private val listener: OnFolderClickListener) : RecyclerView.Adapter<FolderViewHolder>() {
|
|
||||||
|
|
||||||
private val folders: MutableList<Folder> = ArrayList()
|
private val folders: MutableList<Folder> = ArrayList()
|
||||||
|
|
||||||
|
@ -145,7 +143,7 @@ class AddFeedFragment : DialogFragment() {
|
||||||
Collections.sort(folders, Folder.FolderComparator)
|
Collections.sort(folders, Folder.FolderComparator)
|
||||||
this.folders.clear()
|
this.folders.clear()
|
||||||
this.folders.addAll(folders)
|
this.folders.addAll(folders)
|
||||||
notifyDataSetChanged()
|
this.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
class FolderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
class FolderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import com.newsblur.view.StateToggleButton;
|
||||||
import com.newsblur.view.StateToggleButton.StateChangedListener;
|
import com.newsblur.view.StateToggleButton.StateChangedListener;
|
||||||
import com.newsblur.util.StateFilter;
|
import com.newsblur.util.StateFilter;
|
||||||
|
|
||||||
public class FeedIntelligenceSelectorFragment extends Fragment implements StateChangedListener {
|
public class FeedSelectorFragment extends Fragment implements StateChangedListener {
|
||||||
|
|
||||||
private StateToggleButton button;
|
private StateToggleButton button;
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.newsblur.fragment
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import com.newsblur.databinding.FeedsShortcutsDialogBinding
|
||||||
|
|
||||||
|
class FeedsShortcutFragment : DialogFragment() {
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val binding = FeedsShortcutsDialogBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
return AlertDialog.Builder(requireContext()).apply {
|
||||||
|
setView(binding.root)
|
||||||
|
setPositiveButton(android.R.string.ok, null)
|
||||||
|
}.create()
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,6 @@ import android.os.Parcelable;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
@ -31,6 +30,7 @@ import com.newsblur.di.IconLoader;
|
||||||
import com.newsblur.di.ThumbnailLoader;
|
import com.newsblur.di.ThumbnailLoader;
|
||||||
import com.newsblur.domain.Story;
|
import com.newsblur.domain.Story;
|
||||||
import com.newsblur.service.NBSyncService;
|
import com.newsblur.service.NBSyncService;
|
||||||
|
import com.newsblur.util.CursorFilters;
|
||||||
import com.newsblur.util.FeedSet;
|
import com.newsblur.util.FeedSet;
|
||||||
import com.newsblur.util.FeedUtils;
|
import com.newsblur.util.FeedUtils;
|
||||||
import com.newsblur.util.ImageLoader;
|
import com.newsblur.util.ImageLoader;
|
||||||
|
@ -149,15 +149,12 @@ public class ItemSetFragment extends NbFragment {
|
||||||
// disable the throbbers if animations are going to have a zero time scale
|
// disable the throbbers if animations are going to have a zero time scale
|
||||||
boolean isDisableAnimations = ViewUtils.isPowerSaveMode(requireContext());
|
boolean isDisableAnimations = ViewUtils.isPowerSaveMode(requireContext());
|
||||||
|
|
||||||
int[] colorsArray = {ContextCompat.getColor(requireContext(), R.color.refresh_1),
|
int[] colorsArray = UIUtils.getLoadingColorsArray(requireContext());
|
||||||
ContextCompat.getColor(requireContext(), R.color.refresh_2),
|
|
||||||
ContextCompat.getColor(requireContext(), R.color.refresh_3),
|
|
||||||
ContextCompat.getColor(requireContext(), R.color.refresh_4)};
|
|
||||||
binding.topLoadingThrob.setEnabled(!isDisableAnimations);
|
binding.topLoadingThrob.setEnabled(!isDisableAnimations);
|
||||||
binding.topLoadingThrob.setColors(colorsArray);
|
binding.topLoadingThrob.setColors(colorsArray);
|
||||||
|
|
||||||
View footerView = inflater.inflate(R.layout.row_loading_throbber, null);
|
View footerView = inflater.inflate(R.layout.row_loading_throbber, null);
|
||||||
bottomProgressView = (ProgressThrobber) footerView.findViewById(R.id.itemlist_loading_throb);
|
bottomProgressView = footerView.findViewById(R.id.itemlist_loading_throb);
|
||||||
bottomProgressView.setEnabled(!isDisableAnimations);
|
bottomProgressView.setEnabled(!isDisableAnimations);
|
||||||
bottomProgressView.setColors(colorsArray);
|
bottomProgressView.setColors(colorsArray);
|
||||||
|
|
||||||
|
@ -271,7 +268,7 @@ public class ItemSetFragment extends NbFragment {
|
||||||
public void hasUpdated() {
|
public void hasUpdated() {
|
||||||
FeedSet fs = getFeedSet();
|
FeedSet fs = getFeedSet();
|
||||||
if (isAdded() && fs != null) {
|
if (isAdded() && fs != null) {
|
||||||
storiesViewModel.getActiveStories(fs);
|
storiesViewModel.getActiveStories(fs, new CursorFilters(requireContext(), fs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,7 +388,7 @@ public class ItemSetFragment extends NbFragment {
|
||||||
|
|
||||||
// ensure we have measured
|
// ensure we have measured
|
||||||
if (itemGridWidthPx > 0) {
|
if (itemGridWidthPx > 0) {
|
||||||
int itemGridWidthDp = Math.round(UIUtils.px2dp(getActivity(), itemGridWidthPx));
|
int itemGridWidthDp = Math.round(UIUtils.px2dp(requireContext(), itemGridWidthPx));
|
||||||
colsCoarse = itemGridWidthDp / 300;
|
colsCoarse = itemGridWidthDp / 300;
|
||||||
colsMed = itemGridWidthDp / 200;
|
colsMed = itemGridWidthDp / 200;
|
||||||
colsFine = itemGridWidthDp / 150;
|
colsFine = itemGridWidthDp / 150;
|
||||||
|
@ -416,7 +413,7 @@ public class ItemSetFragment extends NbFragment {
|
||||||
if (listStyle == StoryListStyle.LIST) {
|
if (listStyle == StoryListStyle.LIST) {
|
||||||
gridSpacingPx = 0;
|
gridSpacingPx = 0;
|
||||||
} else {
|
} else {
|
||||||
gridSpacingPx = UIUtils.dp2px(getActivity(), GRID_SPACING_DP);
|
gridSpacingPx = UIUtils.dp2px(requireContext(), GRID_SPACING_DP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,8 +432,8 @@ public class ItemSetFragment extends NbFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
RecyclerView.ItemAnimator anim = binding.itemgridfragmentGrid.getItemAnimator();
|
RecyclerView.ItemAnimator anim = binding.itemgridfragmentGrid.getItemAnimator();
|
||||||
anim.setAddDuration((long) ((anim.getAddDuration() + targetAddDuration)/2L));
|
anim.setAddDuration((anim.getAddDuration() + targetAddDuration)/2L);
|
||||||
anim.setMoveDuration((long) ((anim.getMoveDuration() + targetMovDuration)/2L));
|
anim.setMoveDuration((anim.getMoveDuration() + targetMovDuration)/2L);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
private void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
|
|
@ -15,8 +15,7 @@ import com.newsblur.util.setViewVisible
|
||||||
class NewslettersFragment : DialogFragment() {
|
class NewslettersFragment : DialogFragment() {
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val view = layoutInflater.inflate(R.layout.newsletter_dialog, null)
|
val binding = NewsletterDialogBinding.inflate(layoutInflater)
|
||||||
val binding: NewsletterDialogBinding = NewsletterDialogBinding.bind(view)
|
|
||||||
val emailAddress = generateEmail()
|
val emailAddress = generateEmail()
|
||||||
|
|
||||||
binding.txtEmail.text = emailAddress
|
binding.txtEmail.text = emailAddress
|
||||||
|
|
|
@ -47,10 +47,8 @@ abstract class ProfileActivityDetailsFragment : Fragment(), OnItemClickListener
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
val view = inflater.inflate(R.layout.fragment_profileactivity, null)
|
val view = inflater.inflate(R.layout.fragment_profileactivity, null)
|
||||||
binding = FragmentProfileactivityBinding.bind(view)
|
binding = FragmentProfileactivityBinding.bind(view)
|
||||||
val colorsArray = intArrayOf(ContextCompat.getColor(requireContext(), R.color.refresh_1),
|
val colorsArray = UIUtils.getLoadingColorsArray(requireContext())
|
||||||
ContextCompat.getColor(requireContext(), R.color.refresh_2),
|
|
||||||
ContextCompat.getColor(requireContext(), R.color.refresh_3),
|
|
||||||
ContextCompat.getColor(requireContext(), R.color.refresh_4))
|
|
||||||
binding.emptyViewLoadingThrob.setColors(*colorsArray)
|
binding.emptyViewLoadingThrob.setColors(*colorsArray)
|
||||||
binding.profileDetailsActivitylist.setFooterDividersEnabled(false)
|
binding.profileDetailsActivitylist.setFooterDividersEnabled(false)
|
||||||
binding.profileDetailsActivitylist.emptyView = binding.emptyView
|
binding.profileDetailsActivitylist.emptyView = binding.emptyView
|
||||||
|
|
|
@ -15,6 +15,7 @@ import android.webkit.WebView.HitTestResult
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
|
@ -30,6 +31,7 @@ import com.newsblur.di.StoryImageCache
|
||||||
import com.newsblur.domain.Classifier
|
import com.newsblur.domain.Classifier
|
||||||
import com.newsblur.domain.Story
|
import com.newsblur.domain.Story
|
||||||
import com.newsblur.domain.UserDetails
|
import com.newsblur.domain.UserDetails
|
||||||
|
import com.newsblur.keyboard.KeyboardManager
|
||||||
import com.newsblur.network.APIManager
|
import com.newsblur.network.APIManager
|
||||||
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_INTEL
|
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_INTEL
|
||||||
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_SOCIAL
|
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_SOCIAL
|
||||||
|
@ -198,10 +200,10 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding.storyContextMenuButton.setOnClickListener { onClickMenuButton() }
|
binding.storyContextMenuButton.setOnClickListener { onClickMenuButton() }
|
||||||
readingItemActionsBinding.markReadStoryButton.setOnClickListener { clickMarkStoryRead() }
|
readingItemActionsBinding.markReadStoryButton.setOnClickListener { switchMarkStoryReadState() }
|
||||||
readingItemActionsBinding.trainStoryButton.setOnClickListener { clickTrain() }
|
readingItemActionsBinding.trainStoryButton.setOnClickListener { openStoryTrainer() }
|
||||||
readingItemActionsBinding.saveStoryButton.setOnClickListener { clickSave() }
|
readingItemActionsBinding.saveStoryButton.setOnClickListener { switchStorySavedState() }
|
||||||
readingItemActionsBinding.shareStoryButton.setOnClickListener { clickShare() }
|
readingItemActionsBinding.shareStoryButton.setOnClickListener { openShareDialog() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenuInfo?) {
|
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenuInfo?) {
|
||||||
|
@ -262,6 +264,10 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
|
||||||
menu.findItem(R.id.menu_reading_save).setTitle(if (story!!.starred) R.string.menu_unsave_story else R.string.menu_save_story)
|
menu.findItem(R.id.menu_reading_save).setTitle(if (story!!.starred) R.string.menu_unsave_story else R.string.menu_save_story)
|
||||||
if (fs!!.isFilterSaved || fs!!.isAllSaved || fs!!.singleSavedTag != null) menu.findItem(R.id.menu_reading_markunread).isVisible = false
|
if (fs!!.isFilterSaved || fs!!.isAllSaved || fs!!.singleSavedTag != null) menu.findItem(R.id.menu_reading_markunread).isVisible = false
|
||||||
|
|
||||||
|
if (KeyboardManager.hasHardwareKeyboard(requireContext())) {
|
||||||
|
menu.findItem(R.id.menu_shortcuts).isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
when (PrefsUtils.getSelectedTheme(requireContext())) {
|
when (PrefsUtils.getSelectedTheme(requireContext())) {
|
||||||
ThemeValue.LIGHT -> menu.findItem(R.id.menu_theme_light).isChecked = true
|
ThemeValue.LIGHT -> menu.findItem(R.id.menu_theme_light).isChecked = true
|
||||||
ThemeValue.DARK -> menu.findItem(R.id.menu_theme_dark).isChecked = true
|
ThemeValue.DARK -> menu.findItem(R.id.menu_theme_dark).isChecked = true
|
||||||
|
@ -298,8 +304,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
|
||||||
|
|
||||||
override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) {
|
override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) {
|
||||||
R.id.menu_reading_original -> {
|
R.id.menu_reading_original -> {
|
||||||
val uri = Uri.parse(story!!.permalink)
|
openBrowser()
|
||||||
UIUtils.handleUri(requireContext(), uri)
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.menu_reading_sharenewsblur -> {
|
R.id.menu_reading_sharenewsblur -> {
|
||||||
|
@ -317,6 +322,10 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
|
||||||
feedUtils.sendStoryFull(story, requireContext())
|
feedUtils.sendStoryFull(story, requireContext())
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.menu_shortcuts -> {
|
||||||
|
showStoryShortcuts()
|
||||||
|
true
|
||||||
|
}
|
||||||
R.id.menu_text_size_xs -> {
|
R.id.menu_text_size_xs -> {
|
||||||
setTextSizeStyle(ReadingTextSize.XS)
|
setTextSizeStyle(ReadingTextSize.XS)
|
||||||
true
|
true
|
||||||
|
@ -408,7 +417,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
|
||||||
R.id.menu_intel -> {
|
R.id.menu_intel -> {
|
||||||
// check against training on feedless stories
|
// check against training on feedless stories
|
||||||
if (story!!.feedId != "0") {
|
if (story!!.feedId != "0") {
|
||||||
clickTrain()
|
openStoryTrainer()
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -421,9 +430,19 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clickMarkStoryRead() {
|
fun switchMarkStoryReadState(notifyUser: Boolean = false) {
|
||||||
if (story!!.read) feedUtils.markStoryUnread(story!!, requireContext())
|
story?.let {
|
||||||
else feedUtils.markStoryAsRead(story!!, requireContext())
|
val msg = if (it.read) {
|
||||||
|
feedUtils.markStoryUnread(it, requireContext())
|
||||||
|
getString(R.string.story_unread)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
feedUtils.markStoryAsRead(it, requireContext())
|
||||||
|
getString(R.string.story_read)
|
||||||
|
}
|
||||||
|
if (notifyUser) UIUtils.showSnackBar(binding.root, msg)
|
||||||
|
} ?: Log.e(this.javaClass.name, "Error switching null story read state.")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateMarkStoryReadState() {
|
private fun updateMarkStoryReadState() {
|
||||||
|
@ -437,7 +456,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
|
||||||
sampledQueue?.add { updateStoryReadTitleState.invoke() } ?: updateStoryReadTitleState.invoke()
|
sampledQueue?.add { updateStoryReadTitleState.invoke() } ?: updateStoryReadTitleState.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clickTrain() {
|
fun openStoryTrainer() {
|
||||||
val intelFrag = StoryIntelTrainerFragment.newInstance(story, fs)
|
val intelFrag = StoryIntelTrainerFragment.newInstance(story, fs)
|
||||||
intelFrag.show(requireActivity().supportFragmentManager, StoryIntelTrainerFragment::class.java.name)
|
intelFrag.show(requireActivity().supportFragmentManager, StoryIntelTrainerFragment::class.java.name)
|
||||||
}
|
}
|
||||||
|
@ -446,19 +465,25 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
|
||||||
readingItemActionsBinding.trainStoryButton.visibility = if (story!!.feedId == "0") View.GONE else View.VISIBLE
|
readingItemActionsBinding.trainStoryButton.visibility = if (story!!.feedId == "0") View.GONE else View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clickSave() {
|
fun switchStorySavedState(notifyUser: Boolean = false) {
|
||||||
if (story!!.starred) {
|
story?.let {
|
||||||
feedUtils.setStorySaved(story!!.storyHash, false, requireContext())
|
val msg = if (it.starred) {
|
||||||
} else {
|
feedUtils.setStorySaved(it.storyHash, false, requireContext())
|
||||||
feedUtils.setStorySaved(story!!.storyHash, true, requireContext())
|
getString(R.string.story_saved)
|
||||||
}
|
} else {
|
||||||
|
feedUtils.setStorySaved(it.storyHash, true, requireContext())
|
||||||
|
getString(R.string.story_unsaved)
|
||||||
|
}
|
||||||
|
if (notifyUser) UIUtils.showSnackBar(binding.root, msg)
|
||||||
|
|
||||||
|
} ?: Log.e(this.javaClass.name, "Error switching null story saved state.")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSaveButton() {
|
private fun updateSaveButton() {
|
||||||
readingItemActionsBinding.saveStoryButton.setText(if (story!!.starred) R.string.unsave_this else R.string.save_this)
|
readingItemActionsBinding.saveStoryButton.setText(if (story!!.starred) R.string.unsave_this else R.string.save_this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clickShare() {
|
fun openShareDialog() {
|
||||||
val newFragment: DialogFragment = ShareDialogFragment.newInstance(story, sourceUserId)
|
val newFragment: DialogFragment = ShareDialogFragment.newInstance(story, sourceUserId)
|
||||||
newFragment.show(parentFragmentManager, "dialog")
|
newFragment.show(parentFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
|
@ -530,17 +555,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
|
||||||
val intelFrag = StoryIntelTrainerFragment.newInstance(story, fs)
|
val intelFrag = StoryIntelTrainerFragment.newInstance(story, fs)
|
||||||
intelFrag.show(parentFragmentManager, StoryIntelTrainerFragment::class.java.name)
|
intelFrag.show(parentFragmentManager, StoryIntelTrainerFragment::class.java.name)
|
||||||
})
|
})
|
||||||
binding.readingItemTitle.setOnClickListener(object : View.OnClickListener {
|
binding.readingItemTitle.setOnClickListener { openBrowser() }
|
||||||
override fun onClick(v: View) {
|
|
||||||
try {
|
|
||||||
UIUtils.handleUri(requireContext(), Uri.parse(story!!.permalink))
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
// we don't actually know if the user will successfully be able to open whatever string
|
|
||||||
// was in the permalink or if the Intent could throw errors
|
|
||||||
Log.e(this.javaClass.name, "Error opening story by permalink URL.", t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
setupTagsAndIntel()
|
setupTagsAndIntel()
|
||||||
}
|
}
|
||||||
|
@ -963,6 +978,11 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showStoryShortcuts() {
|
||||||
|
val newFragment = StoryShortcutsFragment()
|
||||||
|
newFragment.show(requireActivity().supportFragmentManager, StoryShortcutsFragment::class.java.name)
|
||||||
|
}
|
||||||
|
|
||||||
fun flagWebviewError() {
|
fun flagWebviewError() {
|
||||||
// TODO: enable a selective reload mechanism on load failures?
|
// TODO: enable a selective reload mechanism on load failures?
|
||||||
}
|
}
|
||||||
|
@ -989,8 +1009,31 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
|
||||||
reloadStoryContent()
|
reloadStoryContent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun openBrowser() {
|
||||||
|
story?.let {
|
||||||
|
val uri = Uri.parse(it.permalink)
|
||||||
|
UIUtils.handleUri(requireContext(), uri)
|
||||||
|
} ?: Log.e(this.javaClass.name, "Error opening null story by permalink URL.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun scrollToComments() {
|
||||||
|
val targetView = if (readingItemActionsBinding.readingFriendCommentHeader.isVisible) {
|
||||||
|
readingItemActionsBinding.readingFriendCommentContainer
|
||||||
|
} else if (readingItemActionsBinding.readingPublicCommentHeader.isVisible) {
|
||||||
|
readingItemActionsBinding.readingPublicCommentContainer
|
||||||
|
} else null
|
||||||
|
targetView?.let {
|
||||||
|
it.parent.requestChildFocus(targetView, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun scrollVerticallyBy(dy: Int) {
|
||||||
|
binding.readingScrollview.smoothScrollBy(0, dy)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val BUNDLE_SCROLL_POS_REL = "scrollStateRel"
|
private const val BUNDLE_SCROLL_POS_REL = "scrollStateRel"
|
||||||
|
const val VERTICAL_SCROLL_DISTANCE_DP = 240
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newInstance(story: Story?, feedTitle: String?, feedFaviconColor: String?, feedFaviconFade: String?, feedFaviconBorder: String?, faviconText: String?, faviconUrl: String?, classifier: Classifier?, displayFeedDetails: Boolean, sourceUserId: String?): ReadingItemFragment {
|
fun newInstance(story: Story?, feedTitle: String?, feedFaviconColor: String?, feedFaviconFade: String?, feedFaviconBorder: String?, faviconText: String?, faviconUrl: String?, classifier: Classifier?, displayFeedDetails: Boolean, sourceUserId: String?): ReadingItemFragment {
|
||||||
|
|
|
@ -2,17 +2,16 @@ package com.newsblur.fragment;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
@ -57,6 +56,7 @@ public class StoryIntelTrainerFragment extends DialogFragment {
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -64,9 +64,7 @@ public class StoryIntelTrainerFragment extends DialogFragment {
|
||||||
fs = (FeedSet) getArguments().getSerializable("feedset");
|
fs = (FeedSet) getArguments().getSerializable("feedset");
|
||||||
classifier = dbHelper.getClassifierForFeed(story.feedId);
|
classifier = dbHelper.getClassifierForFeed(story.feedId);
|
||||||
|
|
||||||
final Activity activity = getActivity();
|
View v = getLayoutInflater().inflate(R.layout.dialog_trainstory, null);
|
||||||
LayoutInflater inflater = LayoutInflater.from(activity);
|
|
||||||
View v = inflater.inflate(R.layout.dialog_trainstory, null);
|
|
||||||
binding = DialogTrainstoryBinding.bind(v);
|
binding = DialogTrainstoryBinding.bind(v);
|
||||||
|
|
||||||
// set up the special title training box for the title from this story and the associated buttons
|
// set up the special title training box for the title from this story and the associated buttons
|
||||||
|
@ -111,7 +109,7 @@ public class StoryIntelTrainerFragment extends DialogFragment {
|
||||||
// scan trained title fragments for this feed and see if any apply to this story
|
// scan trained title fragments for this feed and see if any apply to this story
|
||||||
for (Map.Entry<String, Integer> rule : classifier.title.entrySet()) {
|
for (Map.Entry<String, Integer> rule : classifier.title.entrySet()) {
|
||||||
if (story.title.indexOf(rule.getKey()) >= 0) {
|
if (story.title.indexOf(rule.getKey()) >= 0) {
|
||||||
View row = inflater.inflate(R.layout.include_intel_row, null);
|
View row = getLayoutInflater().inflate(R.layout.include_intel_row, null);
|
||||||
TextView label = (TextView) row.findViewById(R.id.intel_row_label);
|
TextView label = (TextView) row.findViewById(R.id.intel_row_label);
|
||||||
label.setText(rule.getKey());
|
label.setText(rule.getKey());
|
||||||
UIUtils.setupIntelDialogRow(row, classifier.title, rule.getKey());
|
UIUtils.setupIntelDialogRow(row, classifier.title, rule.getKey());
|
||||||
|
@ -121,7 +119,7 @@ public class StoryIntelTrainerFragment extends DialogFragment {
|
||||||
|
|
||||||
// list all tags for this story, trained or not
|
// list all tags for this story, trained or not
|
||||||
for (String tag : story.tags) {
|
for (String tag : story.tags) {
|
||||||
View row = inflater.inflate(R.layout.include_intel_row, null);
|
View row = getLayoutInflater().inflate(R.layout.include_intel_row, null);
|
||||||
TextView label = (TextView) row.findViewById(R.id.intel_row_label);
|
TextView label = (TextView) row.findViewById(R.id.intel_row_label);
|
||||||
label.setText(tag);
|
label.setText(tag);
|
||||||
UIUtils.setupIntelDialogRow(row, classifier.tags, tag);
|
UIUtils.setupIntelDialogRow(row, classifier.tags, tag);
|
||||||
|
@ -131,7 +129,7 @@ public class StoryIntelTrainerFragment extends DialogFragment {
|
||||||
|
|
||||||
// there is a single author per story
|
// there is a single author per story
|
||||||
if (!TextUtils.isEmpty(story.authors)) {
|
if (!TextUtils.isEmpty(story.authors)) {
|
||||||
View rowAuthor = inflater.inflate(R.layout.include_intel_row, null);
|
View rowAuthor = getLayoutInflater().inflate(R.layout.include_intel_row, null);
|
||||||
TextView labelAuthor = (TextView) rowAuthor.findViewById(R.id.intel_row_label);
|
TextView labelAuthor = (TextView) rowAuthor.findViewById(R.id.intel_row_label);
|
||||||
labelAuthor.setText(story.authors);
|
labelAuthor.setText(story.authors);
|
||||||
UIUtils.setupIntelDialogRow(rowAuthor, classifier.authors, story.authors);
|
UIUtils.setupIntelDialogRow(rowAuthor, classifier.authors, story.authors);
|
||||||
|
@ -142,13 +140,13 @@ public class StoryIntelTrainerFragment extends DialogFragment {
|
||||||
|
|
||||||
// there is a single feed to be trained, but it is a bit odd in that the label is the title and
|
// there is a single feed to be trained, but it is a bit odd in that the label is the title and
|
||||||
// the intel identifier is the feed ID
|
// the intel identifier is the feed ID
|
||||||
View rowFeed = inflater.inflate(R.layout.include_intel_row, null);
|
View rowFeed = getLayoutInflater().inflate(R.layout.include_intel_row, null);
|
||||||
TextView labelFeed = (TextView) rowFeed.findViewById(R.id.intel_row_label);
|
TextView labelFeed = (TextView) rowFeed.findViewById(R.id.intel_row_label);
|
||||||
labelFeed.setText(feedUtils.getFeedTitle(story.feedId));
|
labelFeed.setText(feedUtils.getFeedTitle(story.feedId));
|
||||||
UIUtils.setupIntelDialogRow(rowFeed, classifier.feeds, story.feedId);
|
UIUtils.setupIntelDialogRow(rowFeed, classifier.feeds, story.feedId);
|
||||||
binding.existingFeedIntelContainer.addView(rowFeed);
|
binding.existingFeedIntelContainer.addView(rowFeed);
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
|
||||||
builder.setTitle(R.string.story_intel_dialog_title);
|
builder.setTitle(R.string.story_intel_dialog_title);
|
||||||
builder.setView(v);
|
builder.setView(v);
|
||||||
|
|
||||||
|
@ -164,7 +162,7 @@ public class StoryIntelTrainerFragment extends DialogFragment {
|
||||||
if ((newTitleTraining != null) && (!TextUtils.isEmpty(binding.intelTitleSelection.getSelection()))) {
|
if ((newTitleTraining != null) && (!TextUtils.isEmpty(binding.intelTitleSelection.getSelection()))) {
|
||||||
classifier.title.put(binding.intelTitleSelection.getSelection(), newTitleTraining);
|
classifier.title.put(binding.intelTitleSelection.getSelection(), newTitleTraining);
|
||||||
}
|
}
|
||||||
feedUtils.updateClassifier(story.feedId, classifier, fs, activity);
|
feedUtils.updateClassifier(story.feedId, classifier, fs, requireActivity());
|
||||||
StoryIntelTrainerFragment.this.dismiss();
|
StoryIntelTrainerFragment.this.dismiss();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.newsblur.fragment
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Spannable
|
||||||
|
import android.text.SpannableString
|
||||||
|
import android.text.style.AbsoluteSizeSpan
|
||||||
|
import android.text.style.StyleSpan
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import com.newsblur.R
|
||||||
|
import com.newsblur.databinding.StoryShortcutsDialogBinding
|
||||||
|
|
||||||
|
class StoryShortcutsFragment : DialogFragment() {
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val binding = StoryShortcutsDialogBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
SpannableString(getString(R.string.short_share_this_story_key)).apply {
|
||||||
|
shiftKeySpannable()
|
||||||
|
}.also {
|
||||||
|
binding.txtShareStoryKey.text = it
|
||||||
|
}
|
||||||
|
|
||||||
|
SpannableString(getString(R.string.short_page_up_key)).apply {
|
||||||
|
shiftKeySpannable()
|
||||||
|
}.also {
|
||||||
|
binding.txtPageUpKey.text = it
|
||||||
|
}
|
||||||
|
|
||||||
|
return AlertDialog.Builder(requireContext()).apply {
|
||||||
|
setView(binding.root)
|
||||||
|
setPositiveButton(android.R.string.ok, null)
|
||||||
|
}.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SpannableString.shiftKeySpannable() {
|
||||||
|
setSpan(AbsoluteSizeSpan(18, true),
|
||||||
|
0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
|
||||||
|
setSpan(StyleSpan(Typeface.BOLD),
|
||||||
|
0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.newsblur.keyboard
|
||||||
|
|
||||||
|
interface KeyboardListener {
|
||||||
|
|
||||||
|
fun onKeyboardEvent(event: KeyboardEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class KeyboardEvent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyboard events for Home
|
||||||
|
*/
|
||||||
|
object OpenAllStories : KeyboardEvent()
|
||||||
|
object AddFeed : KeyboardEvent()
|
||||||
|
object SwitchViewRight : KeyboardEvent()
|
||||||
|
object SwitchViewLeft : KeyboardEvent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyboard events for Reading
|
||||||
|
*/
|
||||||
|
object NextStory : KeyboardEvent()
|
||||||
|
object PreviousStory : KeyboardEvent()
|
||||||
|
object ToggleTextView : KeyboardEvent()
|
||||||
|
object NextUnreadStory : KeyboardEvent()
|
||||||
|
object ToggleReadUnread : KeyboardEvent()
|
||||||
|
object SaveUnsaveStory : KeyboardEvent()
|
||||||
|
object OpenInBrowser : KeyboardEvent()
|
||||||
|
object ShareStory : KeyboardEvent()
|
||||||
|
object ScrollToComments : KeyboardEvent()
|
||||||
|
object OpenStoryTrainer : KeyboardEvent()
|
||||||
|
object PageDown: KeyboardEvent()
|
||||||
|
object PageUp: KeyboardEvent()
|
||||||
|
|
||||||
|
object Tutorial: KeyboardEvent()
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
package com.newsblur.keyboard
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.view.KeyEvent
|
||||||
|
|
||||||
|
class KeyboardManager {
|
||||||
|
|
||||||
|
private var listener: KeyboardListener? = null
|
||||||
|
|
||||||
|
fun addListener(listener: KeyboardListener) {
|
||||||
|
this.listener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeListener() {
|
||||||
|
this.listener = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Return <code>true</code> to prevent this event from being propagated
|
||||||
|
* further, or <code>false</code> to indicate that you have not handled
|
||||||
|
*/
|
||||||
|
fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean = when (keyCode) {
|
||||||
|
/**
|
||||||
|
* Home events
|
||||||
|
*/
|
||||||
|
KeyEvent.KEYCODE_E -> {
|
||||||
|
handleKeycodeE(event)
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_A -> {
|
||||||
|
handleKeycodeA(event)
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
||||||
|
listener?.onKeyboardEvent(KeyboardEvent.SwitchViewRight)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_DPAD_LEFT -> {
|
||||||
|
listener?.onKeyboardEvent(KeyboardEvent.SwitchViewLeft)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Story events
|
||||||
|
*/
|
||||||
|
KeyEvent.KEYCODE_J,
|
||||||
|
KeyEvent.KEYCODE_DPAD_DOWN -> {
|
||||||
|
listener?.onKeyboardEvent(KeyboardEvent.PreviousStory)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_K,
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP -> {
|
||||||
|
listener?.onKeyboardEvent(KeyboardEvent.NextStory)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_N -> {
|
||||||
|
listener?.onKeyboardEvent(KeyboardEvent.NextUnreadStory)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_U, KeyEvent.KEYCODE_M -> {
|
||||||
|
listener?.onKeyboardEvent(KeyboardEvent.ToggleReadUnread)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_S -> {
|
||||||
|
if (event.isShiftPressed) listener?.onKeyboardEvent(KeyboardEvent.ShareStory)
|
||||||
|
else listener?.onKeyboardEvent(KeyboardEvent.SaveUnsaveStory)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_V -> {
|
||||||
|
listener?.onKeyboardEvent(KeyboardEvent.OpenInBrowser)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_C -> {
|
||||||
|
listener?.onKeyboardEvent(KeyboardEvent.ScrollToComments)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_T -> {
|
||||||
|
listener?.onKeyboardEvent(KeyboardEvent.OpenStoryTrainer)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_ENTER,
|
||||||
|
KeyEvent.KEYCODE_NUMPAD_ENTER -> {
|
||||||
|
if (event.isShiftPressed) {
|
||||||
|
listener?.onKeyboardEvent(KeyboardEvent.ToggleTextView)
|
||||||
|
true
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_SPACE -> {
|
||||||
|
if (event.isShiftPressed) listener?.onKeyboardEvent(KeyboardEvent.PageUp)
|
||||||
|
else listener?.onKeyboardEvent(KeyboardEvent.PageDown)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_ALT_RIGHT,
|
||||||
|
KeyEvent.KEYCODE_ALT_LEFT -> {
|
||||||
|
listener?.onKeyboardEvent(KeyboardEvent.Tutorial)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleKeycodeE(event: KeyEvent): Boolean = if (event.isAltPressed) {
|
||||||
|
listener?.onKeyboardEvent(KeyboardEvent.OpenAllStories)
|
||||||
|
true
|
||||||
|
} else false
|
||||||
|
|
||||||
|
private fun handleKeycodeA(event: KeyEvent): Boolean = if (event.isAltPressed) {
|
||||||
|
listener?.onKeyboardEvent(KeyboardEvent.AddFeed)
|
||||||
|
true
|
||||||
|
} else false
|
||||||
|
|
||||||
|
fun isKnownKeyCode(keyCode: Int): Boolean =
|
||||||
|
isShortcutKeyCode(keyCode) && isSpecialKeyCode(keyCode)
|
||||||
|
|
||||||
|
private fun isSpecialKeyCode(keyCode: Int) = when (keyCode) {
|
||||||
|
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||||
|
KeyEvent.KEYCODE_DPAD_RIGHT,
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP,
|
||||||
|
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||||
|
KeyEvent.KEYCODE_ENTER,
|
||||||
|
KeyEvent.KEYCODE_NUMPAD_ENTER,
|
||||||
|
KeyEvent.KEYCODE_SPACE,
|
||||||
|
-> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isShortcutKeyCode(keyCode: Int) = when (keyCode) {
|
||||||
|
KeyEvent.KEYCODE_E,
|
||||||
|
KeyEvent.KEYCODE_A,
|
||||||
|
KeyEvent.KEYCODE_J,
|
||||||
|
KeyEvent.KEYCODE_K,
|
||||||
|
KeyEvent.KEYCODE_N,
|
||||||
|
KeyEvent.KEYCODE_U,
|
||||||
|
KeyEvent.KEYCODE_M,
|
||||||
|
KeyEvent.KEYCODE_S,
|
||||||
|
KeyEvent.KEYCODE_O,
|
||||||
|
KeyEvent.KEYCODE_V,
|
||||||
|
KeyEvent.KEYCODE_C,
|
||||||
|
KeyEvent.KEYCODE_T,
|
||||||
|
-> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun hasHardwareKeyboard(context: Context) =
|
||||||
|
context.resources.configuration.keyboard == Configuration.KEYBOARD_QWERTY
|
||||||
|
}
|
||||||
|
}
|
|
@ -205,7 +205,7 @@ public class APIManager {
|
||||||
public ProfileResponse updateUserProfile() {
|
public ProfileResponse updateUserProfile() {
|
||||||
final APIResponse response = get(buildUrl(APIConstants.PATH_MY_PROFILE));
|
final APIResponse response = get(buildUrl(APIConstants.PATH_MY_PROFILE));
|
||||||
if (!response.isError()) {
|
if (!response.isError()) {
|
||||||
ProfileResponse profileResponse = (ProfileResponse) response.getResponse(gson, ProfileResponse.class);
|
ProfileResponse profileResponse = response.getResponse(gson, ProfileResponse.class);
|
||||||
PrefsUtils.saveUserDetails(context, profileResponse.user);
|
PrefsUtils.saveUserDetails(context, profileResponse.user);
|
||||||
return profileResponse;
|
return profileResponse;
|
||||||
} else {
|
} else {
|
||||||
|
@ -234,14 +234,14 @@ public class APIManager {
|
||||||
values.put(APIConstants.PARAMETER_FEEDID, id);
|
values.put(APIConstants.PARAMETER_FEEDID, id);
|
||||||
}
|
}
|
||||||
APIResponse response = get(buildUrl(APIConstants.PATH_FEED_UNREAD_COUNT), values);
|
APIResponse response = get(buildUrl(APIConstants.PATH_FEED_UNREAD_COUNT), values);
|
||||||
return (UnreadCountResponse) response.getResponse(gson, UnreadCountResponse.class);
|
return response.getResponse(gson, UnreadCountResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UnreadStoryHashesResponse getUnreadStoryHashes() {
|
public UnreadStoryHashesResponse getUnreadStoryHashes() {
|
||||||
ValueMultimap values = new ValueMultimap();
|
ValueMultimap values = new ValueMultimap();
|
||||||
values.put(APIConstants.PARAMETER_INCLUDE_TIMESTAMPS, "1");
|
values.put(APIConstants.PARAMETER_INCLUDE_TIMESTAMPS, "1");
|
||||||
APIResponse response = get(buildUrl(APIConstants.PATH_UNREAD_HASHES), values);
|
APIResponse response = get(buildUrl(APIConstants.PATH_UNREAD_HASHES), values);
|
||||||
return (UnreadStoryHashesResponse) response.getResponse(gson, UnreadStoryHashesResponse.class);
|
return response.getResponse(gson, UnreadStoryHashesResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StarredStoryHashesResponse getStarredStoryHashes() {
|
public StarredStoryHashesResponse getStarredStoryHashes() {
|
||||||
|
@ -256,7 +256,7 @@ public class APIManager {
|
||||||
}
|
}
|
||||||
values.put(APIConstants.PARAMETER_INCLUDE_HIDDEN, APIConstants.VALUE_TRUE);
|
values.put(APIConstants.PARAMETER_INCLUDE_HIDDEN, APIConstants.VALUE_TRUE);
|
||||||
APIResponse response = get(buildUrl(APIConstants.PATH_RIVER_STORIES), values);
|
APIResponse response = get(buildUrl(APIConstants.PATH_RIVER_STORIES), values);
|
||||||
return (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
|
return response.getResponse(gson, StoriesResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -264,7 +264,7 @@ public class APIManager {
|
||||||
* request parameters as needed.
|
* request parameters as needed.
|
||||||
*/
|
*/
|
||||||
public StoriesResponse getStories(FeedSet fs, int pageNumber, StoryOrder order, ReadFilter filter) {
|
public StoriesResponse getStories(FeedSet fs, int pageNumber, StoryOrder order, ReadFilter filter) {
|
||||||
Uri uri = null;
|
Uri uri;
|
||||||
ValueMultimap values = new ValueMultimap();
|
ValueMultimap values = new ValueMultimap();
|
||||||
|
|
||||||
// create the URI and populate request params depending on what kind of stories we want
|
// create the URI and populate request params depending on what kind of stories we want
|
||||||
|
@ -331,29 +331,21 @@ public class APIManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
APIResponse response = get(uri.toString(), values);
|
APIResponse response = get(uri.toString(), values);
|
||||||
return (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
|
return response.getResponse(gson, StoriesResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean followUser(final String userId) {
|
public boolean followUser(final String userId) {
|
||||||
final ContentValues values = new ContentValues();
|
final ContentValues values = new ContentValues();
|
||||||
values.put(APIConstants.PARAMETER_USERID, userId);
|
values.put(APIConstants.PARAMETER_USERID, userId);
|
||||||
final APIResponse response = post(buildUrl(APIConstants.PATH_FOLLOW), values);
|
final APIResponse response = post(buildUrl(APIConstants.PATH_FOLLOW), values);
|
||||||
if (!response.isError()) {
|
return !response.isError();
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean unfollowUser(final String userId) {
|
public boolean unfollowUser(final String userId) {
|
||||||
final ContentValues values = new ContentValues();
|
final ContentValues values = new ContentValues();
|
||||||
values.put(APIConstants.PARAMETER_USERID, userId);
|
values.put(APIConstants.PARAMETER_USERID, userId);
|
||||||
final APIResponse response = post(buildUrl(APIConstants.PATH_UNFOLLOW), values);
|
final APIResponse response = post(buildUrl(APIConstants.PATH_UNFOLLOW), values);
|
||||||
if (!response.isError()) {
|
return !response.isError();
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public APIResponse saveExternalStory(@NonNull String storyTitle, @NonNull String storyUrl) {
|
public APIResponse saveExternalStory(@NonNull String storyTitle, @NonNull String storyUrl) {
|
||||||
|
@ -386,7 +378,7 @@ public class APIManager {
|
||||||
|
|
||||||
APIResponse response = post(buildUrl(APIConstants.PATH_SHARE_STORY), values);
|
APIResponse response = post(buildUrl(APIConstants.PATH_SHARE_STORY), values);
|
||||||
// this call returns a new copy of the story with all fields updated and some metadata
|
// this call returns a new copy of the story with all fields updated and some metadata
|
||||||
return (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
|
return response.getResponse(gson, StoriesResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StoriesResponse unshareStory(String storyId, String feedId) {
|
public StoriesResponse unshareStory(String storyId, String feedId) {
|
||||||
|
@ -396,7 +388,7 @@ public class APIManager {
|
||||||
|
|
||||||
APIResponse response = post(buildUrl(APIConstants.PATH_UNSHARE_STORY), values);
|
APIResponse response = post(buildUrl(APIConstants.PATH_UNSHARE_STORY), values);
|
||||||
// this call returns a new copy of the story with all fields updated and some metadata
|
// this call returns a new copy of the story with all fields updated and some metadata
|
||||||
return (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
|
return response.getResponse(gson, StoriesResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -438,8 +430,7 @@ public class APIManager {
|
||||||
values.put(APIConstants.PARAMETER_USER_ID, userId);
|
values.put(APIConstants.PARAMETER_USER_ID, userId);
|
||||||
final APIResponse response = get(buildUrl(APIConstants.PATH_USER_PROFILE), values);
|
final APIResponse response = get(buildUrl(APIConstants.PATH_USER_PROFILE), values);
|
||||||
if (!response.isError()) {
|
if (!response.isError()) {
|
||||||
ProfileResponse profileResponse = (ProfileResponse) response.getResponse(gson, ProfileResponse.class);
|
return response.getResponse(gson, ProfileResponse.class);
|
||||||
return profileResponse;
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -452,8 +443,7 @@ public class APIManager {
|
||||||
values.put(APIConstants.PARAMETER_PAGE_NUMBER, Integer.toString(pageNumber));
|
values.put(APIConstants.PARAMETER_PAGE_NUMBER, Integer.toString(pageNumber));
|
||||||
final APIResponse response = get(buildUrl(APIConstants.PATH_USER_ACTIVITIES), values);
|
final APIResponse response = get(buildUrl(APIConstants.PATH_USER_ACTIVITIES), values);
|
||||||
if (!response.isError()) {
|
if (!response.isError()) {
|
||||||
ActivitiesResponse activitiesResponse = (ActivitiesResponse) response.getResponse(gson, ActivitiesResponse.class);
|
return response.getResponse(gson, ActivitiesResponse.class);
|
||||||
return activitiesResponse;
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -466,8 +456,7 @@ public class APIManager {
|
||||||
values.put(APIConstants.PARAMETER_PAGE_NUMBER, Integer.toString(pageNumber));
|
values.put(APIConstants.PARAMETER_PAGE_NUMBER, Integer.toString(pageNumber));
|
||||||
final APIResponse response = get(buildUrl(APIConstants.PATH_USER_INTERACTIONS), values);
|
final APIResponse response = get(buildUrl(APIConstants.PATH_USER_INTERACTIONS), values);
|
||||||
if (!response.isError()) {
|
if (!response.isError()) {
|
||||||
InteractionsResponse interactionsResponse = (InteractionsResponse) response.getResponse(gson, InteractionsResponse.class);
|
return response.getResponse(gson, InteractionsResponse.class);
|
||||||
return interactionsResponse;
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -479,8 +468,7 @@ public class APIManager {
|
||||||
values.put(APIConstants.PARAMETER_STORYID, storyId);
|
values.put(APIConstants.PARAMETER_STORYID, storyId);
|
||||||
final APIResponse response = get(buildUrl(APIConstants.PATH_STORY_TEXT), values);
|
final APIResponse response = get(buildUrl(APIConstants.PATH_STORY_TEXT), values);
|
||||||
if (!response.isError()) {
|
if (!response.isError()) {
|
||||||
StoryTextResponse storyTextResponse = (StoryTextResponse) response.getResponse(gson, StoryTextResponse.class);
|
return response.getResponse(gson, StoryTextResponse.class);
|
||||||
return storyTextResponse;
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -520,7 +508,7 @@ public class APIManager {
|
||||||
values.put(APIConstants.PARAMETER_REPLY_TEXT, reply);
|
values.put(APIConstants.PARAMETER_REPLY_TEXT, reply);
|
||||||
APIResponse response = post(buildUrl(APIConstants.PATH_REPLY_TO), values);
|
APIResponse response = post(buildUrl(APIConstants.PATH_REPLY_TO), values);
|
||||||
// this call returns a new copy of the comment with all fields updated
|
// this call returns a new copy of the comment with all fields updated
|
||||||
return (CommentResponse) response.getResponse(gson, CommentResponse.class);
|
return response.getResponse(gson, CommentResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommentResponse editReply(String storyId, String storyFeedId, String commentUserId, String replyId, String reply) {
|
public CommentResponse editReply(String storyId, String storyFeedId, String commentUserId, String replyId, String reply) {
|
||||||
|
@ -532,7 +520,7 @@ public class APIManager {
|
||||||
values.put(APIConstants.PARAMETER_REPLY_TEXT, reply);
|
values.put(APIConstants.PARAMETER_REPLY_TEXT, reply);
|
||||||
APIResponse response = post(buildUrl(APIConstants.PATH_EDIT_REPLY), values);
|
APIResponse response = post(buildUrl(APIConstants.PATH_EDIT_REPLY), values);
|
||||||
// this call returns a new copy of the comment with all fields updated
|
// this call returns a new copy of the comment with all fields updated
|
||||||
return (CommentResponse) response.getResponse(gson, CommentResponse.class);
|
return response.getResponse(gson, CommentResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommentResponse deleteReply(String storyId, String storyFeedId, String commentUserId, String replyId) {
|
public CommentResponse deleteReply(String storyId, String storyFeedId, String commentUserId, String replyId) {
|
||||||
|
@ -543,7 +531,7 @@ public class APIManager {
|
||||||
values.put(APIConstants.PARAMETER_REPLY_ID, replyId);
|
values.put(APIConstants.PARAMETER_REPLY_ID, replyId);
|
||||||
APIResponse response = post(buildUrl(APIConstants.PATH_DELETE_REPLY), values);
|
APIResponse response = post(buildUrl(APIConstants.PATH_DELETE_REPLY), values);
|
||||||
// this call returns a new copy of the comment with all fields updated
|
// this call returns a new copy of the comment with all fields updated
|
||||||
return (CommentResponse) response.getResponse(gson, CommentResponse.class);
|
return response.getResponse(gson, CommentResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NewsBlurResponse addFolder(String folderName) {
|
public NewsBlurResponse addFolder(String folderName) {
|
||||||
|
@ -711,10 +699,10 @@ public class APIManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String builderGetParametersString(ContentValues values) {
|
private String builderGetParametersString(ContentValues values) {
|
||||||
List<String> parameters = new ArrayList<String>();
|
List<String> parameters = new ArrayList<>();
|
||||||
for (Entry<String, Object> entry : values.valueSet()) {
|
for (Entry<String, Object> entry : values.valueSet()) {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
builder.append((String) entry.getKey());
|
builder.append(entry.getKey());
|
||||||
builder.append("=");
|
builder.append("=");
|
||||||
builder.append(NetworkUtils.encodeURL((String) entry.getValue()));
|
builder.append(NetworkUtils.encodeURL((String) entry.getValue()));
|
||||||
parameters.add(builder.toString());
|
parameters.add(builder.toString());
|
||||||
|
@ -749,7 +737,7 @@ public class APIManager {
|
||||||
formBody.writeTo(buffer);
|
formBody.writeTo(buffer);
|
||||||
body = buffer.readUtf8();
|
body = buffer.readUtf8();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
; // this is debug code, do not raise
|
// this is debug code, do not raise
|
||||||
}
|
}
|
||||||
Log.d(this.getClass().getName(), "post body: " + body);
|
Log.d(this.getClass().getName(), "post body: " + body);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package com.newsblur.network;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
@ -109,7 +108,6 @@ public class APIResponse {
|
||||||
* may be used for calls that return data, or the parent class may be used if no
|
* may be used for calls that return data, or the parent class may be used if no
|
||||||
* return data are expected.
|
* return data are expected.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <T extends NewsBlurResponse> T getResponse(Gson gson, Class<T> classOfT) {
|
public <T extends NewsBlurResponse> T getResponse(Gson gson, Class<T> classOfT) {
|
||||||
if (this.isError) {
|
if (this.isError) {
|
||||||
// if we encountered an error, make a generic response type and populate
|
// if we encountered an error, make a generic response type and populate
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.newsblur.service;
|
package com.newsblur.service;
|
||||||
|
|
||||||
|
import com.newsblur.util.ExtensionsKt;
|
||||||
import com.newsblur.util.PrefConstants;
|
import com.newsblur.util.PrefConstants;
|
||||||
import com.newsblur.util.PrefsUtils;
|
import com.newsblur.util.PrefsUtils;
|
||||||
|
|
||||||
|
@ -8,7 +9,7 @@ public class CleanupService extends SubService {
|
||||||
public static boolean activelyRunning = false;
|
public static boolean activelyRunning = false;
|
||||||
|
|
||||||
public CleanupService(NBSyncService parent) {
|
public CleanupService(NBSyncService parent) {
|
||||||
super(parent);
|
super(parent, ExtensionsKt.NBScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.newsblur.service;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.newsblur.util.AppConstants;
|
import com.newsblur.util.AppConstants;
|
||||||
|
import com.newsblur.util.ExtensionsKt;
|
||||||
import com.newsblur.util.PrefsUtils;
|
import com.newsblur.util.PrefsUtils;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -19,7 +20,7 @@ public class ImagePrefetchService extends SubService {
|
||||||
static Set<String> ThumbnailQueue = Collections.synchronizedSet(new HashSet<>());
|
static Set<String> ThumbnailQueue = Collections.synchronizedSet(new HashSet<>());
|
||||||
|
|
||||||
public ImagePrefetchService(NBSyncService parent) {
|
public ImagePrefetchService(NBSyncService parent) {
|
||||||
super(parent);
|
super(parent, ExtensionsKt.NBScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -33,7 +34,6 @@ public class ImagePrefetchService extends SubService {
|
||||||
if (! PrefsUtils.isImagePrefetchEnabled(parent)) return;
|
if (! PrefsUtils.isImagePrefetchEnabled(parent)) return;
|
||||||
if (! PrefsUtils.isBackgroundNetworkAllowed(parent)) return;
|
if (! PrefsUtils.isBackgroundNetworkAllowed(parent)) return;
|
||||||
|
|
||||||
startExpensiveCycle();
|
|
||||||
com.newsblur.util.Log.d(this, "story images to prefetch: " + StoryImageQueue.size());
|
com.newsblur.util.Log.d(this, "story images to prefetch: " + StoryImageQueue.size());
|
||||||
// on each batch, re-query the DB for images associated with yet-unread stories
|
// on each batch, re-query the DB for images associated with yet-unread stories
|
||||||
// this is a bit expensive, but we are running totally async at a really low priority
|
// this is a bit expensive, but we are running totally async at a really low priority
|
||||||
|
@ -66,7 +66,6 @@ public class ImagePrefetchService extends SubService {
|
||||||
if (! PrefsUtils.isImagePrefetchEnabled(parent)) return;
|
if (! PrefsUtils.isImagePrefetchEnabled(parent)) return;
|
||||||
if (! PrefsUtils.isBackgroundNetworkAllowed(parent)) return;
|
if (! PrefsUtils.isBackgroundNetworkAllowed(parent)) return;
|
||||||
|
|
||||||
startExpensiveCycle();
|
|
||||||
com.newsblur.util.Log.d(this, "story thumbs to prefetch: " + StoryImageQueue.size());
|
com.newsblur.util.Log.d(this, "story thumbs to prefetch: " + StoryImageQueue.size());
|
||||||
// on each batch, re-query the DB for images associated with yet-unread stories
|
// on each batch, re-query the DB for images associated with yet-unread stories
|
||||||
// this is a bit expensive, but we are running totally async at a really low priority
|
// this is a bit expensive, but we are running totally async at a really low priority
|
||||||
|
|
|
@ -38,6 +38,7 @@ import com.newsblur.network.domain.NewsBlurResponse;
|
||||||
import com.newsblur.network.domain.StoriesResponse;
|
import com.newsblur.network.domain.StoriesResponse;
|
||||||
import com.newsblur.network.domain.UnreadCountResponse;
|
import com.newsblur.network.domain.UnreadCountResponse;
|
||||||
import com.newsblur.util.AppConstants;
|
import com.newsblur.util.AppConstants;
|
||||||
|
import com.newsblur.util.CursorFilters;
|
||||||
import com.newsblur.util.DefaultFeedView;
|
import com.newsblur.util.DefaultFeedView;
|
||||||
import com.newsblur.util.FeedSet;
|
import com.newsblur.util.FeedSet;
|
||||||
import com.newsblur.util.FileCache;
|
import com.newsblur.util.FileCache;
|
||||||
|
@ -49,6 +50,7 @@ import com.newsblur.util.ReadingAction;
|
||||||
import com.newsblur.util.ReadFilter;
|
import com.newsblur.util.ReadFilter;
|
||||||
import com.newsblur.util.StateFilter;
|
import com.newsblur.util.StateFilter;
|
||||||
import com.newsblur.util.StoryOrder;
|
import com.newsblur.util.StoryOrder;
|
||||||
|
import com.newsblur.util.UIUtils;
|
||||||
import com.newsblur.widget.WidgetUtils;
|
import com.newsblur.widget.WidgetUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -411,6 +413,8 @@ public class NBSyncService extends JobService {
|
||||||
|
|
||||||
ActionsRunning = true;
|
ActionsRunning = true;
|
||||||
|
|
||||||
|
StateFilter stateFilter = PrefsUtils.getStateFilter(this);
|
||||||
|
|
||||||
actionsloop : while (c.moveToNext()) {
|
actionsloop : while (c.moveToNext()) {
|
||||||
sendSyncUpdate(UPDATE_STATUS);
|
sendSyncUpdate(UPDATE_STATUS);
|
||||||
String id = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_ID));
|
String id = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_ID));
|
||||||
|
@ -427,7 +431,7 @@ public class NBSyncService extends JobService {
|
||||||
if ((ra.getTried() > 0) && (PendingFeed != null)) continue actionsloop;
|
if ((ra.getTried() > 0) && (PendingFeed != null)) continue actionsloop;
|
||||||
|
|
||||||
com.newsblur.util.Log.d(this, "attempting action: " + ra.toContentValues().toString());
|
com.newsblur.util.Log.d(this, "attempting action: " + ra.toContentValues().toString());
|
||||||
NewsBlurResponse response = ra.doRemote(apiManager, dbHelper);
|
NewsBlurResponse response = ra.doRemote(apiManager, dbHelper, stateFilter);
|
||||||
|
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
com.newsblur.util.Log.e(this.getClass().getName(), "Discarding reading action with client-side error.");
|
com.newsblur.util.Log.e(this.getClass().getName(), "Discarding reading action with client-side error.");
|
||||||
|
@ -471,7 +475,7 @@ public class NBSyncService extends JobService {
|
||||||
Log.d(this, "double-checking " + FollowupActions.size() + " actions");
|
Log.d(this, "double-checking " + FollowupActions.size() + " actions");
|
||||||
int impactFlags = 0;
|
int impactFlags = 0;
|
||||||
for (ReadingAction ra : FollowupActions) {
|
for (ReadingAction ra : FollowupActions) {
|
||||||
int impact = ra.doLocal(dbHelper, true);
|
int impact = ra.doLocal(this, dbHelper, true);
|
||||||
impactFlags |= impact;
|
impactFlags |= impact;
|
||||||
}
|
}
|
||||||
sendSyncUpdate(impactFlags);
|
sendSyncUpdate(impactFlags);
|
||||||
|
@ -766,7 +770,7 @@ public class NBSyncService extends JobService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareReadingSession(dbHelper, fs);
|
prepareReadingSession(this, dbHelper, fs);
|
||||||
|
|
||||||
LastFeedSet = fs;
|
LastFeedSet = fs;
|
||||||
|
|
||||||
|
@ -785,8 +789,7 @@ public class NBSyncService extends JobService {
|
||||||
int pageNumber = FeedPagesSeen.get(fs);
|
int pageNumber = FeedPagesSeen.get(fs);
|
||||||
int totalStoriesSeen = FeedStoriesSeen.get(fs);
|
int totalStoriesSeen = FeedStoriesSeen.get(fs);
|
||||||
|
|
||||||
StoryOrder order = PrefsUtils.getStoryOrder(this, fs);
|
CursorFilters cursorFilters = new CursorFilters(this, fs);
|
||||||
ReadFilter filter = PrefsUtils.getReadFilter(this, fs);
|
|
||||||
|
|
||||||
StorySyncRunning = true;
|
StorySyncRunning = true;
|
||||||
sendSyncUpdate(UPDATE_STATUS);
|
sendSyncUpdate(UPDATE_STATUS);
|
||||||
|
@ -802,7 +805,7 @@ public class NBSyncService extends JobService {
|
||||||
}
|
}
|
||||||
|
|
||||||
pageNumber++;
|
pageNumber++;
|
||||||
StoriesResponse apiResponse = apiManager.getStories(fs, pageNumber, order, filter);
|
StoriesResponse apiResponse = apiManager.getStories(fs, pageNumber, cursorFilters.getStoryOrder(), cursorFilters.getReadFilter());
|
||||||
|
|
||||||
if (! isStoryResponseGood(apiResponse)) return;
|
if (! isStoryResponseGood(apiResponse)) return;
|
||||||
|
|
||||||
|
@ -810,7 +813,7 @@ public class NBSyncService extends JobService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
insertStories(apiResponse, fs);
|
insertStories(apiResponse, fs, cursorFilters.getStateFilter());
|
||||||
// re-do any very recent actions that were incorrectly overwritten by this page
|
// re-do any very recent actions that were incorrectly overwritten by this page
|
||||||
finishActions();
|
finishActions();
|
||||||
sendSyncUpdate(UPDATE_STORY | UPDATE_STATUS);
|
sendSyncUpdate(UPDATE_STORY | UPDATE_STATUS);
|
||||||
|
@ -855,7 +858,7 @@ public class NBSyncService extends JobService {
|
||||||
private long workaroundReadStoryTimestamp;
|
private long workaroundReadStoryTimestamp;
|
||||||
private long workaroundGloblaSharedStoryTimestamp;
|
private long workaroundGloblaSharedStoryTimestamp;
|
||||||
|
|
||||||
private void insertStories(StoriesResponse apiResponse, FeedSet fs) {
|
private void insertStories(StoriesResponse apiResponse, FeedSet fs, StateFilter stateFilter) {
|
||||||
if (fs.isAllRead()) {
|
if (fs.isAllRead()) {
|
||||||
// Ugly Hack Warning: the API doesn't vend the sortation key necessary to display
|
// Ugly Hack Warning: the API doesn't vend the sortation key necessary to display
|
||||||
// stories when in the "read stories" view. It does, however, return them in the
|
// stories when in the "read stories" view. It does, however, return them in the
|
||||||
|
@ -919,12 +922,12 @@ public class NBSyncService extends JobService {
|
||||||
}
|
}
|
||||||
|
|
||||||
com.newsblur.util.Log.d(NBSyncService.class.getName(), "got stories from main fetch loop: " + apiResponse.stories.length);
|
com.newsblur.util.Log.d(NBSyncService.class.getName(), "got stories from main fetch loop: " + apiResponse.stories.length);
|
||||||
dbHelper.insertStories(apiResponse, true);
|
dbHelper.insertStories(apiResponse, stateFilter, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void insertStories(StoriesResponse apiResponse) {
|
void insertStories(StoriesResponse apiResponse, StateFilter stateFilter) {
|
||||||
com.newsblur.util.Log.d(NBSyncService.class.getName(), "got stories from sub sync: " + apiResponse.stories.length);
|
com.newsblur.util.Log.d(NBSyncService.class.getName(), "got stories from sub sync: " + apiResponse.stories.length);
|
||||||
dbHelper.insertStories(apiResponse, false);
|
dbHelper.insertStories(apiResponse, stateFilter, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void prefetchOriginalText(StoriesResponse apiResponse) {
|
void prefetchOriginalText(StoriesResponse apiResponse) {
|
||||||
|
@ -1144,8 +1147,9 @@ public class NBSyncService extends JobService {
|
||||||
* set but also when we sync a page of stories, since there are no guarantees which
|
* set but also when we sync a page of stories, since there are no guarantees which
|
||||||
* will happen first.
|
* will happen first.
|
||||||
*/
|
*/
|
||||||
public static void prepareReadingSession(BlurDatabaseHelper dbHelper, FeedSet fs) {
|
public static void prepareReadingSession(Context context, BlurDatabaseHelper dbHelper, FeedSet fs) {
|
||||||
synchronized (PENDING_FEED_MUTEX) {
|
synchronized (PENDING_FEED_MUTEX) {
|
||||||
|
CursorFilters cursorFilters = new CursorFilters(context, fs);
|
||||||
if (! fs.equals(dbHelper.getSessionFeedSet())) {
|
if (! fs.equals(dbHelper.getSessionFeedSet())) {
|
||||||
com.newsblur.util.Log.d(NBSyncService.class.getName(), "preparing new reading session");
|
com.newsblur.util.Log.d(NBSyncService.class.getName(), "preparing new reading session");
|
||||||
// the next fetch will be the start of a new reading session; clear it so it
|
// the next fetch will be the start of a new reading session; clear it so it
|
||||||
|
@ -1153,10 +1157,10 @@ public class NBSyncService extends JobService {
|
||||||
dbHelper.clearStorySession();
|
dbHelper.clearStorySession();
|
||||||
// don't just rely on the auto-prepare code when fetching stories, it might be called
|
// don't just rely on the auto-prepare code when fetching stories, it might be called
|
||||||
// after we insert our first page and not trigger
|
// after we insert our first page and not trigger
|
||||||
dbHelper.prepareReadingSession(fs);
|
dbHelper.prepareReadingSession(fs, cursorFilters.getStateFilter(), cursorFilters.getReadFilter());
|
||||||
// note which feedset we are loading so we can trigger another reset when it changes
|
// note which feedset we are loading so we can trigger another reset when it changes
|
||||||
dbHelper.setSessionFeedSet(fs);
|
dbHelper.setSessionFeedSet(fs);
|
||||||
dbHelper.sendSyncUpdate(UPDATE_STORY | UPDATE_STATUS);
|
UIUtils.syncUpdateStatus(context, UPDATE_STORY | UPDATE_STATUS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import static com.newsblur.service.NBSyncReceiver.UPDATE_TEXT;
|
||||||
import com.newsblur.database.DatabaseConstants;
|
import com.newsblur.database.DatabaseConstants;
|
||||||
import com.newsblur.network.domain.StoryTextResponse;
|
import com.newsblur.network.domain.StoryTextResponse;
|
||||||
import com.newsblur.util.AppConstants;
|
import com.newsblur.util.AppConstants;
|
||||||
|
import com.newsblur.util.ExtensionsKt;
|
||||||
import com.newsblur.util.FeedUtils;
|
import com.newsblur.util.FeedUtils;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -22,14 +23,12 @@ public class OriginalTextService extends SubService {
|
||||||
private static final Pattern imgSniff = Pattern.compile("<img[^>]*src=(['\"])((?:(?!\\1).)*)\\1[^>]*>", Pattern.CASE_INSENSITIVE);
|
private static final Pattern imgSniff = Pattern.compile("<img[^>]*src=(['\"])((?:(?!\\1).)*)\\1[^>]*>", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
/** story hashes we need to fetch (from newly found stories) */
|
/** story hashes we need to fetch (from newly found stories) */
|
||||||
private static Set<String> Hashes;
|
private static final Set<String> Hashes = new HashSet<>();
|
||||||
static {Hashes = new HashSet<String>();}
|
|
||||||
/** story hashes we should fetch ASAP (they are waiting on-screen) */
|
/** story hashes we should fetch ASAP (they are waiting on-screen) */
|
||||||
private static Set<String> PriorityHashes;
|
private static final Set<String> PriorityHashes = new HashSet<>();
|
||||||
static {PriorityHashes = new HashSet<String>();}
|
|
||||||
|
|
||||||
public OriginalTextService(NBSyncService parent) {
|
public OriginalTextService(NBSyncService parent) {
|
||||||
super(parent);
|
super(parent, ExtensionsKt.NBScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,129 +0,0 @@
|
||||||
package com.newsblur.service;
|
|
||||||
|
|
||||||
import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
|
|
||||||
|
|
||||||
import android.os.Process;
|
|
||||||
|
|
||||||
import com.newsblur.NbApplication;
|
|
||||||
import com.newsblur.util.AppConstants;
|
|
||||||
import com.newsblur.util.Log;
|
|
||||||
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A utility construct to make NbSyncService a bit more modular by encapsulating sync tasks
|
|
||||||
* that can be run fully asynchronously from the main sync loop. Like all of the sync service,
|
|
||||||
* flags and data used by these modules need to be static so that parts of the app without a
|
|
||||||
* handle to the service object can access them.
|
|
||||||
*/
|
|
||||||
public abstract class SubService {
|
|
||||||
|
|
||||||
protected NBSyncService parent;
|
|
||||||
private ThreadPoolExecutor executor;
|
|
||||||
private long cycleStartTime = 0L;
|
|
||||||
|
|
||||||
private SubService() {
|
|
||||||
; // no default construction
|
|
||||||
}
|
|
||||||
|
|
||||||
SubService(NBSyncService parent) {
|
|
||||||
this.parent = parent;
|
|
||||||
executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
Runnable r = new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (parent.stopSync()) return;
|
|
||||||
if (!NbApplication.isAppForeground()) {
|
|
||||||
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_LESS_FAVORABLE );
|
|
||||||
} else {
|
|
||||||
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE + Process.THREAD_PRIORITY_LESS_FAVORABLE );
|
|
||||||
}
|
|
||||||
Thread.currentThread().setName(this.getClass().getName());
|
|
||||||
exec_();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
executor.execute(r);
|
|
||||||
// enqueue a check task that will run strictly after the real one, so the callback
|
|
||||||
// can effectively check queue size to see if there are queued tasks
|
|
||||||
executor.execute(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
parent.checkCompletion();
|
|
||||||
parent.sendSyncUpdate(UPDATE_STATUS);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (RejectedExecutionException ree) {
|
|
||||||
// this is perfectly normal, as service soft-stop mechanics might have shut down our thread pool
|
|
||||||
// while peer subservices are still running
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void exec_() {
|
|
||||||
try {
|
|
||||||
exec();
|
|
||||||
cycleStartTime = 0;
|
|
||||||
} catch (Exception e) {
|
|
||||||
com.newsblur.util.Log.e(this.getClass().getName(), "Sync error.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void exec();
|
|
||||||
|
|
||||||
public void shutdown() {
|
|
||||||
Log.d(this, "SubService stopping");
|
|
||||||
executor.shutdown();
|
|
||||||
try {
|
|
||||||
executor.awaitTermination(AppConstants.SHUTDOWN_SLACK_SECONDS, TimeUnit.SECONDS);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
executor.shutdownNow();
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
} finally {
|
|
||||||
Log.d(this, "SubService stopped");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRunning() {
|
|
||||||
// don't advise completion until there are no tasks, or just one check task left
|
|
||||||
return (executor.getQueue().size() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If called at the beginning of an expensive loop in a SubService, enforces the maximum duty cycle
|
|
||||||
* defined in AppConstants by sleeping for a short while so the SubService does not dominate system
|
|
||||||
* resources.
|
|
||||||
*/
|
|
||||||
protected void startExpensiveCycle() {
|
|
||||||
if (cycleStartTime == 0) {
|
|
||||||
cycleStartTime = System.nanoTime();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
double lastCycleTime = (System.nanoTime() - cycleStartTime);
|
|
||||||
if (lastCycleTime < 1) return;
|
|
||||||
|
|
||||||
cycleStartTime = System.nanoTime();
|
|
||||||
|
|
||||||
double cooloffTime = lastCycleTime * (1.0 - AppConstants.MAX_BG_DUTY_CYCLE);
|
|
||||||
if (cooloffTime < 1) return;
|
|
||||||
long cooloffTimeMs = Math.round(cooloffTime / 1000000.0);
|
|
||||||
if (cooloffTimeMs > AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS) cooloffTimeMs = AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS;
|
|
||||||
|
|
||||||
if (NbApplication.isAppForeground()) {
|
|
||||||
com.newsblur.util.Log.d(this.getClass().getName(), "Sleeping for : " + cooloffTimeMs + "ms to enforce max duty cycle.");
|
|
||||||
try {
|
|
||||||
Thread.sleep(cooloffTimeMs);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
package com.newsblur.service
|
||||||
|
|
||||||
|
import com.newsblur.util.Log
|
||||||
|
import com.newsblur.util.NBScope
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.ensureActive
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.concurrent.CancellationException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility construct to make NbSyncService a bit more modular by encapsulating sync tasks
|
||||||
|
* that can be run fully asynchronously from the main sync loop. Like all of the sync service,
|
||||||
|
* flags and data used by these modules need to be static so that parts of the app without a
|
||||||
|
* handle to the service object can access them.
|
||||||
|
*/
|
||||||
|
abstract class SubService(
|
||||||
|
@JvmField
|
||||||
|
protected val parent: NBSyncService,
|
||||||
|
private val coroutineScope: CoroutineScope = NBScope,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private var mainJob: Job? = null
|
||||||
|
|
||||||
|
protected abstract fun exec()
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
mainJob = coroutineScope.launch(Dispatchers.IO) {
|
||||||
|
if (parent.stopSync()) return@launch
|
||||||
|
|
||||||
|
Thread.currentThread().name = this@SubService.javaClass.name
|
||||||
|
execInternal()
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
|
parent.checkCompletion()
|
||||||
|
parent.sendSyncUpdate(NBSyncReceiver.UPDATE_STATUS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun execInternal() = coroutineScope {
|
||||||
|
try {
|
||||||
|
ensureActive()
|
||||||
|
exec()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(this@SubService.javaClass.name, "Sync error.", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shutdown() {
|
||||||
|
Log.d(this, "SubService shutdown")
|
||||||
|
try {
|
||||||
|
mainJob?.cancel()
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
Log.d(this, "SubService cancelled")
|
||||||
|
} finally {
|
||||||
|
Log.d(this, "SubService stopped")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isRunning: Boolean
|
||||||
|
get() = mainJob?.isActive ?: false
|
||||||
|
}
|
|
@ -24,8 +24,6 @@ import kotlinx.coroutines.launch
|
||||||
*/
|
*/
|
||||||
class SubscriptionSyncService : JobService() {
|
class SubscriptionSyncService : JobService() {
|
||||||
|
|
||||||
private val scope = NBScope
|
|
||||||
|
|
||||||
override fun onStartJob(params: JobParameters?): Boolean {
|
override fun onStartJob(params: JobParameters?): Boolean {
|
||||||
Log.d(this, "onStartJob")
|
Log.d(this, "onStartJob")
|
||||||
if (!PrefsUtils.hasCookie(this)) {
|
if (!PrefsUtils.hasCookie(this)) {
|
||||||
|
@ -33,10 +31,10 @@ class SubscriptionSyncService : JobService() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val subscriptionManager = SubscriptionManagerImpl(this@SubscriptionSyncService, scope)
|
val subscriptionManager = SubscriptionManagerImpl(this@SubscriptionSyncService)
|
||||||
subscriptionManager.startBillingConnection(object : SubscriptionsListener {
|
subscriptionManager.startBillingConnection(object : SubscriptionsListener {
|
||||||
override fun onBillingConnectionReady() {
|
override fun onBillingConnectionReady() {
|
||||||
scope.launch {
|
NBScope.launch {
|
||||||
subscriptionManager.syncActiveSubscription()
|
subscriptionManager.syncActiveSubscription()
|
||||||
Log.d(this, "sync active subscription completed.")
|
Log.d(this, "sync active subscription completed.")
|
||||||
// manually call jobFinished after work is done
|
// manually call jobFinished after work is done
|
||||||
|
|
|
@ -3,8 +3,10 @@ package com.newsblur.service;
|
||||||
import com.newsblur.network.domain.StoriesResponse;
|
import com.newsblur.network.domain.StoriesResponse;
|
||||||
import com.newsblur.network.domain.UnreadStoryHashesResponse;
|
import com.newsblur.network.domain.UnreadStoryHashesResponse;
|
||||||
import com.newsblur.util.AppConstants;
|
import com.newsblur.util.AppConstants;
|
||||||
|
import com.newsblur.util.ExtensionsKt;
|
||||||
import com.newsblur.util.FeedUtils;
|
import com.newsblur.util.FeedUtils;
|
||||||
import com.newsblur.util.PrefsUtils;
|
import com.newsblur.util.PrefsUtils;
|
||||||
|
import com.newsblur.util.StateFilter;
|
||||||
import com.newsblur.util.StoryOrder;
|
import com.newsblur.util.StoryOrder;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -25,7 +27,7 @@ public class UnreadsService extends SubService {
|
||||||
static { StoryHashQueue = new ArrayList<String>(); }
|
static { StoryHashQueue = new ArrayList<String>(); }
|
||||||
|
|
||||||
public UnreadsService(NBSyncService parent) {
|
public UnreadsService(NBSyncService parent) {
|
||||||
super(parent);
|
super(parent, ExtensionsKt.NBScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -137,8 +139,6 @@ public class UnreadsService extends SubService {
|
||||||
boolean isTextPrefetchEnabled = PrefsUtils.isTextPrefetchEnabled(parent);
|
boolean isTextPrefetchEnabled = PrefsUtils.isTextPrefetchEnabled(parent);
|
||||||
if (! (isOfflineEnabled || isEnableNotifications)) return;
|
if (! (isOfflineEnabled || isEnableNotifications)) return;
|
||||||
|
|
||||||
startExpensiveCycle();
|
|
||||||
|
|
||||||
List<String> hashBatch = new ArrayList(AppConstants.UNREAD_FETCH_BATCH_SIZE);
|
List<String> hashBatch = new ArrayList(AppConstants.UNREAD_FETCH_BATCH_SIZE);
|
||||||
List<String> hashSkips = new ArrayList(AppConstants.UNREAD_FETCH_BATCH_SIZE);
|
List<String> hashSkips = new ArrayList(AppConstants.UNREAD_FETCH_BATCH_SIZE);
|
||||||
batchloop: for (String hash : StoryHashQueue) {
|
batchloop: for (String hash : StoryHashQueue) {
|
||||||
|
@ -156,7 +156,8 @@ public class UnreadsService extends SubService {
|
||||||
break unreadsyncloop;
|
break unreadsyncloop;
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.insertStories(response);
|
StateFilter stateFilter = PrefsUtils.getStateFilter(parent);
|
||||||
|
parent.insertStories(response, stateFilter);
|
||||||
for (String hash : hashBatch) {
|
for (String hash : hashBatch) {
|
||||||
StoryHashQueue.remove(hash);
|
StoryHashQueue.remove(hash);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.newsblur.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
data class CursorFilters(
|
||||||
|
val stateFilter: StateFilter,
|
||||||
|
val readFilter: ReadFilter,
|
||||||
|
val storyOrder: StoryOrder,
|
||||||
|
) {
|
||||||
|
|
||||||
|
constructor(context: Context, fs: FeedSet) : this(
|
||||||
|
stateFilter = PrefsUtils.getStateFilter(context),
|
||||||
|
readFilter = PrefsUtils.getReadFilter(context, fs),
|
||||||
|
storyOrder = PrefsUtils.getStoryOrder(context, fs),
|
||||||
|
)
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ fun <R> CoroutineScope.executeAsyncTask(
|
||||||
withContext(Dispatchers.Main) { onPostExecute(result) }
|
withContext(Dispatchers.Main) { onPostExecute(result) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmField
|
||||||
val NBScope = CoroutineScope(
|
val NBScope = CoroutineScope(
|
||||||
CoroutineName(TAG) +
|
CoroutineName(TAG) +
|
||||||
Dispatchers.Default +
|
Dispatchers.Default +
|
||||||
|
|
|
@ -34,12 +34,14 @@ class FeedUtils(
|
||||||
@JvmField
|
@JvmField
|
||||||
var currentFolderName: String? = null
|
var currentFolderName: String? = null
|
||||||
|
|
||||||
fun prepareReadingSession(fs: FeedSet?, resetFirst: Boolean) {
|
fun prepareReadingSession(context: Context, fs: FeedSet?, resetFirst: Boolean) {
|
||||||
NBScope.executeAsyncTask(
|
NBScope.executeAsyncTask(
|
||||||
doInBackground = {
|
doInBackground = {
|
||||||
try {
|
try {
|
||||||
if (resetFirst) NBSyncService.resetReadingSession(dbHelper)
|
if (resetFirst) NBSyncService.resetReadingSession(dbHelper)
|
||||||
NBSyncService.prepareReadingSession(dbHelper, fs)
|
fs?.let {
|
||||||
|
NBSyncService.prepareReadingSession(context, dbHelper, it)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// this is a UI hinting call and might fail if the DB is being reset, but that is fine
|
// this is a UI hinting call and might fail if the DB is being reset, but that is fine
|
||||||
}
|
}
|
||||||
|
@ -63,7 +65,7 @@ class FeedUtils(
|
||||||
NBScope.executeAsyncTask(
|
NBScope.executeAsyncTask(
|
||||||
doInBackground = {
|
doInBackground = {
|
||||||
val ra = if (saved) ReadingAction.saveStory(storyHash, userTags) else ReadingAction.unsaveStory(storyHash)
|
val ra = if (saved) ReadingAction.saveStory(storyHash, userTags) else ReadingAction.unsaveStory(storyHash)
|
||||||
ra.doLocal(dbHelper)
|
ra.doLocal(context, dbHelper)
|
||||||
syncUpdateStatus(context, UPDATE_STORY)
|
syncUpdateStatus(context, UPDATE_STORY)
|
||||||
dbHelper.enqueueAction(ra)
|
dbHelper.enqueueAction(ra)
|
||||||
triggerSync(context)
|
triggerSync(context)
|
||||||
|
@ -306,7 +308,7 @@ class FeedUtils(
|
||||||
NBScope.executeAsyncTask(
|
NBScope.executeAsyncTask(
|
||||||
doInBackground = {
|
doInBackground = {
|
||||||
dbHelper.enqueueAction(ra)
|
dbHelper.enqueueAction(ra)
|
||||||
val impact = ra.doLocal(dbHelper)
|
val impact = ra.doLocal(context, dbHelper)
|
||||||
syncUpdateStatus(context, impact)
|
syncUpdateStatus(context, impact)
|
||||||
triggerSync(context)
|
triggerSync(context)
|
||||||
}
|
}
|
||||||
|
@ -347,7 +349,7 @@ class FeedUtils(
|
||||||
}
|
}
|
||||||
val ra = ReadingAction.shareStory(story.storyHash, story.id, story.feedId, sourceUserId, comment)
|
val ra = ReadingAction.shareStory(story.storyHash, story.id, story.feedId, sourceUserId, comment)
|
||||||
dbHelper.enqueueAction(ra)
|
dbHelper.enqueueAction(ra)
|
||||||
ra.doLocal(dbHelper)
|
ra.doLocal(context, dbHelper)
|
||||||
syncUpdateStatus(context, UPDATE_SOCIAL or UPDATE_STORY)
|
syncUpdateStatus(context, UPDATE_SOCIAL or UPDATE_STORY)
|
||||||
triggerSync(context)
|
triggerSync(context)
|
||||||
}
|
}
|
||||||
|
@ -355,7 +357,7 @@ class FeedUtils(
|
||||||
fun renameFeed(context: Context, feedId: String?, newFeedName: String?) {
|
fun renameFeed(context: Context, feedId: String?, newFeedName: String?) {
|
||||||
val ra = ReadingAction.renameFeed(feedId, newFeedName)
|
val ra = ReadingAction.renameFeed(feedId, newFeedName)
|
||||||
dbHelper.enqueueAction(ra)
|
dbHelper.enqueueAction(ra)
|
||||||
val impact = ra.doLocal(dbHelper)
|
val impact = ra.doLocal(context, dbHelper)
|
||||||
syncUpdateStatus(context, impact)
|
syncUpdateStatus(context, impact)
|
||||||
triggerSync(context)
|
triggerSync(context)
|
||||||
}
|
}
|
||||||
|
@ -363,7 +365,7 @@ class FeedUtils(
|
||||||
fun unshareStory(story: Story, context: Context) {
|
fun unshareStory(story: Story, context: Context) {
|
||||||
val ra = ReadingAction.unshareStory(story.storyHash, story.id, story.feedId)
|
val ra = ReadingAction.unshareStory(story.storyHash, story.id, story.feedId)
|
||||||
dbHelper.enqueueAction(ra)
|
dbHelper.enqueueAction(ra)
|
||||||
ra.doLocal(dbHelper)
|
ra.doLocal(context, dbHelper)
|
||||||
syncUpdateStatus(context, UPDATE_SOCIAL or UPDATE_STORY)
|
syncUpdateStatus(context, UPDATE_SOCIAL or UPDATE_STORY)
|
||||||
triggerSync(context)
|
triggerSync(context)
|
||||||
}
|
}
|
||||||
|
@ -371,7 +373,7 @@ class FeedUtils(
|
||||||
fun likeComment(story: Story, commentUserId: String?, context: Context) {
|
fun likeComment(story: Story, commentUserId: String?, context: Context) {
|
||||||
val ra = ReadingAction.likeComment(story.id, commentUserId, story.feedId)
|
val ra = ReadingAction.likeComment(story.id, commentUserId, story.feedId)
|
||||||
dbHelper.enqueueAction(ra)
|
dbHelper.enqueueAction(ra)
|
||||||
ra.doLocal(dbHelper)
|
ra.doLocal(context, dbHelper)
|
||||||
syncUpdateStatus(context, UPDATE_SOCIAL)
|
syncUpdateStatus(context, UPDATE_SOCIAL)
|
||||||
triggerSync(context)
|
triggerSync(context)
|
||||||
}
|
}
|
||||||
|
@ -379,7 +381,7 @@ class FeedUtils(
|
||||||
fun unlikeComment(story: Story, commentUserId: String?, context: Context) {
|
fun unlikeComment(story: Story, commentUserId: String?, context: Context) {
|
||||||
val ra = ReadingAction.unlikeComment(story.id, commentUserId, story.feedId)
|
val ra = ReadingAction.unlikeComment(story.id, commentUserId, story.feedId)
|
||||||
dbHelper.enqueueAction(ra)
|
dbHelper.enqueueAction(ra)
|
||||||
ra.doLocal(dbHelper)
|
ra.doLocal(context, dbHelper)
|
||||||
syncUpdateStatus(context, UPDATE_SOCIAL)
|
syncUpdateStatus(context, UPDATE_SOCIAL)
|
||||||
triggerSync(context)
|
triggerSync(context)
|
||||||
}
|
}
|
||||||
|
@ -387,7 +389,7 @@ class FeedUtils(
|
||||||
fun replyToComment(storyId: String?, feedId: String?, commentUserId: String?, replyText: String?, context: Context) {
|
fun replyToComment(storyId: String?, feedId: String?, commentUserId: String?, replyText: String?, context: Context) {
|
||||||
val ra = ReadingAction.replyToComment(storyId, feedId, commentUserId, replyText)
|
val ra = ReadingAction.replyToComment(storyId, feedId, commentUserId, replyText)
|
||||||
dbHelper.enqueueAction(ra)
|
dbHelper.enqueueAction(ra)
|
||||||
ra.doLocal(dbHelper)
|
ra.doLocal(context, dbHelper)
|
||||||
syncUpdateStatus(context, UPDATE_SOCIAL)
|
syncUpdateStatus(context, UPDATE_SOCIAL)
|
||||||
triggerSync(context)
|
triggerSync(context)
|
||||||
}
|
}
|
||||||
|
@ -395,7 +397,7 @@ class FeedUtils(
|
||||||
fun updateReply(context: Context, story: Story, commentUserId: String?, replyId: String?, replyText: String?) {
|
fun updateReply(context: Context, story: Story, commentUserId: String?, replyId: String?, replyText: String?) {
|
||||||
val ra = ReadingAction.updateReply(story.id, story.feedId, commentUserId, replyId, replyText)
|
val ra = ReadingAction.updateReply(story.id, story.feedId, commentUserId, replyId, replyText)
|
||||||
dbHelper.enqueueAction(ra)
|
dbHelper.enqueueAction(ra)
|
||||||
ra.doLocal(dbHelper)
|
ra.doLocal(context, dbHelper)
|
||||||
syncUpdateStatus(context, UPDATE_SOCIAL)
|
syncUpdateStatus(context, UPDATE_SOCIAL)
|
||||||
triggerSync(context)
|
triggerSync(context)
|
||||||
}
|
}
|
||||||
|
@ -403,7 +405,7 @@ class FeedUtils(
|
||||||
fun deleteReply(context: Context, story: Story, commentUserId: String?, replyId: String?) {
|
fun deleteReply(context: Context, story: Story, commentUserId: String?, replyId: String?) {
|
||||||
val ra = ReadingAction.deleteReply(story.id, story.feedId, commentUserId, replyId)
|
val ra = ReadingAction.deleteReply(story.id, story.feedId, commentUserId, replyId)
|
||||||
dbHelper.enqueueAction(ra)
|
dbHelper.enqueueAction(ra)
|
||||||
ra.doLocal(dbHelper)
|
ra.doLocal(context, dbHelper)
|
||||||
syncUpdateStatus(context, UPDATE_SOCIAL)
|
syncUpdateStatus(context, UPDATE_SOCIAL)
|
||||||
triggerSync(context)
|
triggerSync(context)
|
||||||
}
|
}
|
||||||
|
@ -448,7 +450,7 @@ class FeedUtils(
|
||||||
}
|
}
|
||||||
|
|
||||||
dbHelper.enqueueAction(ra)
|
dbHelper.enqueueAction(ra)
|
||||||
ra.doLocal(dbHelper)
|
ra.doLocal(context, dbHelper)
|
||||||
|
|
||||||
syncUpdateStatus(context, UPDATE_METADATA)
|
syncUpdateStatus(context, UPDATE_METADATA)
|
||||||
triggerSync(context)
|
triggerSync(context)
|
||||||
|
@ -459,7 +461,7 @@ class FeedUtils(
|
||||||
fun instaFetchFeed(context: Context, feedId: String?) {
|
fun instaFetchFeed(context: Context, feedId: String?) {
|
||||||
val ra = ReadingAction.instaFetch(feedId)
|
val ra = ReadingAction.instaFetch(feedId)
|
||||||
dbHelper.enqueueAction(ra)
|
dbHelper.enqueueAction(ra)
|
||||||
ra.doLocal(dbHelper)
|
ra.doLocal(context, dbHelper)
|
||||||
syncUpdateStatus(context, UPDATE_METADATA)
|
syncUpdateStatus(context, UPDATE_METADATA)
|
||||||
triggerSync(context)
|
triggerSync(context)
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,8 +103,6 @@ public class NotificationUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addAction deprecated in 23 but replacement not avail until 21
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
private static Notification buildStoryNotification(Story story, Cursor cursor, Context context, FileCache iconCache) {
|
private static Notification buildStoryNotification(Story story, Cursor cursor, Context context, FileCache iconCache) {
|
||||||
Log.d(NotificationUtils.class.getName(), "Building notification");
|
Log.d(NotificationUtils.class.getName(), "Building notification");
|
||||||
Intent i = new Intent(context, FeedReading.class);
|
Intent i = new Intent(context, FeedReading.class);
|
||||||
|
|
|
@ -22,6 +22,8 @@ import android.graphics.BitmapFactory;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -40,7 +42,7 @@ public class PrefsUtils {
|
||||||
|
|
||||||
private PrefsUtils() {} // util class - no instances
|
private PrefsUtils() {} // util class - no instances
|
||||||
|
|
||||||
public static void saveCustomServer(Context context, String customServer) {
|
public static void saveCustomServer(Context context, @Nullable String customServer) {
|
||||||
if (customServer == null) return;
|
if (customServer == null) return;
|
||||||
if (customServer.length() <= 0) return;
|
if (customServer.length() <= 0) return;
|
||||||
SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
|
SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
|
||||||
|
@ -88,7 +90,7 @@ public class PrefsUtils {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void updateVersion(Context context, String appVersion) {
|
public static void updateVersion(Context context, @Nullable String appVersion) {
|
||||||
SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
|
SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
|
||||||
// store the current version
|
// store the current version
|
||||||
prefs.edit().putString(AppConstants.LAST_APP_VERSION, appVersion).commit();
|
prefs.edit().putString(AppConstants.LAST_APP_VERSION, appVersion).commit();
|
||||||
|
@ -96,6 +98,7 @@ public class PrefsUtils {
|
||||||
prefs.edit().putLong(AppConstants.LAST_SYNC_TIME, 0L).commit();
|
prefs.edit().putLong(AppConstants.LAST_SYNC_TIME, 0L).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public static String getVersion(Context context) {
|
public static String getVersion(Context context) {
|
||||||
try {
|
try {
|
||||||
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
|
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
|
||||||
|
@ -105,7 +108,7 @@ public class PrefsUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String createFeedbackLink(Context context, BlurDatabaseHelper dbHelper) {
|
public static String createFeedbackLink(Context context, @NonNull BlurDatabaseHelper dbHelper) {
|
||||||
StringBuilder s = new StringBuilder(AppConstants.FEEDBACK_URL);
|
StringBuilder s = new StringBuilder(AppConstants.FEEDBACK_URL);
|
||||||
s.append("<give us some feedback!>%0A%0A%0A");
|
s.append("<give us some feedback!>%0A%0A%0A");
|
||||||
String info = getDebugInfo(context, dbHelper);
|
String info = getDebugInfo(context, dbHelper);
|
||||||
|
@ -113,7 +116,7 @@ public class PrefsUtils {
|
||||||
return s.toString();
|
return s.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void sendLogEmail(Context context, BlurDatabaseHelper dbHelper) {
|
public static void sendLogEmail(Context context, @NonNull BlurDatabaseHelper dbHelper) {
|
||||||
File f = com.newsblur.util.Log.getLogfile();
|
File f = com.newsblur.util.Log.getLogfile();
|
||||||
if (f == null) return;
|
if (f == null) return;
|
||||||
String debugInfo = "Tell us a bit about your problem:\n\n\n\n" + getDebugInfo(context, dbHelper);
|
String debugInfo = "Tell us a bit about your problem:\n\n\n\n" + getDebugInfo(context, dbHelper);
|
||||||
|
@ -129,7 +132,7 @@ public class PrefsUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getDebugInfo(Context context, BlurDatabaseHelper dbHelper) {
|
private static String getDebugInfo(Context context, @NonNull BlurDatabaseHelper dbHelper) {
|
||||||
StringBuilder s = new StringBuilder();
|
StringBuilder s = new StringBuilder();
|
||||||
s.append("app version: ").append(getVersion(context));
|
s.append("app version: ").append(getVersion(context));
|
||||||
s.append("\n");
|
s.append("\n");
|
||||||
|
@ -167,7 +170,7 @@ public class PrefsUtils {
|
||||||
return s.toString();
|
return s.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void logout(Context context, BlurDatabaseHelper dbHelper) {
|
public static void logout(Context context, @NonNull BlurDatabaseHelper dbHelper) {
|
||||||
NBSyncService.softInterrupt();
|
NBSyncService.softInterrupt();
|
||||||
NBSyncService.clearState();
|
NBSyncService.clearState();
|
||||||
|
|
||||||
|
@ -194,7 +197,7 @@ public class PrefsUtils {
|
||||||
context.startActivity(i);
|
context.startActivity(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void clearPrefsAndDbForLoginAs(Context context, BlurDatabaseHelper dbHelper) {
|
public static void clearPrefsAndDbForLoginAs(Context context, @NonNull BlurDatabaseHelper dbHelper) {
|
||||||
NBSyncService.softInterrupt();
|
NBSyncService.softInterrupt();
|
||||||
NBSyncService.clearState();
|
NBSyncService.clearState();
|
||||||
|
|
||||||
|
@ -252,6 +255,7 @@ public class PrefsUtils {
|
||||||
saveUserImage(context, profile.photoUrl);
|
saveUserImage(context, profile.photoUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public static String getUserId(Context context) {
|
public static String getUserId(Context context) {
|
||||||
SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
|
SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
|
||||||
return preferences.getString(PrefConstants.USER_ID, null);
|
return preferences.getString(PrefConstants.USER_ID, null);
|
||||||
|
@ -282,7 +286,7 @@ public class PrefsUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void saveUserImage(final Context context, String pictureUrl) {
|
private static void saveUserImage(final Context context, String pictureUrl) {
|
||||||
Bitmap bitmap = null;
|
Bitmap bitmap;
|
||||||
try {
|
try {
|
||||||
URL url = new URL(pictureUrl);
|
URL url = new URL(pictureUrl);
|
||||||
URLConnection connection;
|
URLConnection connection;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import static com.newsblur.service.NBSyncReceiver.UPDATE_SOCIAL;
|
||||||
import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
|
import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
@ -277,7 +278,7 @@ public class ReadingAction implements Serializable {
|
||||||
/**
|
/**
|
||||||
* Execute this action remotely via the API.
|
* Execute this action remotely via the API.
|
||||||
*/
|
*/
|
||||||
public NewsBlurResponse doRemote(APIManager apiManager, BlurDatabaseHelper dbHelper) {
|
public NewsBlurResponse doRemote(APIManager apiManager, BlurDatabaseHelper dbHelper, StateFilter stateFilter) {
|
||||||
// generic response to return
|
// generic response to return
|
||||||
NewsBlurResponse result = null;
|
NewsBlurResponse result = null;
|
||||||
// optional specific responses that are locally actionable
|
// optional specific responses that are locally actionable
|
||||||
|
@ -370,7 +371,7 @@ public class ReadingAction implements Serializable {
|
||||||
if (storiesResponse != null) {
|
if (storiesResponse != null) {
|
||||||
result = storiesResponse;
|
result = storiesResponse;
|
||||||
if (storiesResponse.story != null) {
|
if (storiesResponse.story != null) {
|
||||||
dbHelper.updateStory(storiesResponse, true);
|
dbHelper.updateStory(storiesResponse, stateFilter, true);
|
||||||
} else {
|
} else {
|
||||||
com.newsblur.util.Log.w(this, "failed to refresh story data after action");
|
com.newsblur.util.Log.w(this, "failed to refresh story data after action");
|
||||||
}
|
}
|
||||||
|
@ -391,8 +392,8 @@ public class ReadingAction implements Serializable {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int doLocal(BlurDatabaseHelper dbHelper) {
|
public int doLocal(Context context, BlurDatabaseHelper dbHelper) {
|
||||||
return doLocal(dbHelper, false);
|
return doLocal(context, dbHelper, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -402,7 +403,8 @@ public class ReadingAction implements Serializable {
|
||||||
*
|
*
|
||||||
* @return the union of update impact flags that resulted from this action.
|
* @return the union of update impact flags that resulted from this action.
|
||||||
*/
|
*/
|
||||||
public int doLocal(BlurDatabaseHelper dbHelper, boolean isFollowup) {
|
public int doLocal(Context context, BlurDatabaseHelper dbHelper, boolean isFollowup) {
|
||||||
|
String userId = PrefsUtils.getUserId(context);
|
||||||
int impact = 0;
|
int impact = 0;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
||||||
|
@ -434,32 +436,32 @@ public class ReadingAction implements Serializable {
|
||||||
|
|
||||||
case SHARE:
|
case SHARE:
|
||||||
if (isFollowup) break; // shares are only placeholders
|
if (isFollowup) break; // shares are only placeholders
|
||||||
dbHelper.setStoryShared(storyHash, true);
|
dbHelper.setStoryShared(storyHash, userId, true);
|
||||||
dbHelper.insertCommentPlaceholder(storyId, feedId, commentReplyText);
|
dbHelper.insertCommentPlaceholder(storyId, userId, commentReplyText);
|
||||||
impact |= UPDATE_SOCIAL;
|
impact |= UPDATE_SOCIAL;
|
||||||
impact |= UPDATE_STORY;
|
impact |= UPDATE_STORY;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UNSHARE:
|
case UNSHARE:
|
||||||
dbHelper.setStoryShared(storyHash, false);
|
dbHelper.setStoryShared(storyHash, userId, false);
|
||||||
dbHelper.clearSelfComments(storyId);
|
dbHelper.clearSelfComments(storyId, userId);
|
||||||
impact |= UPDATE_SOCIAL;
|
impact |= UPDATE_SOCIAL;
|
||||||
impact |= UPDATE_STORY;
|
impact |= UPDATE_STORY;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LIKE_COMMENT:
|
case LIKE_COMMENT:
|
||||||
dbHelper.setCommentLiked(storyId, commentUserId, feedId, true);
|
dbHelper.setCommentLiked(storyId, commentUserId, userId, true);
|
||||||
impact |= UPDATE_SOCIAL;
|
impact |= UPDATE_SOCIAL;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UNLIKE_COMMENT:
|
case UNLIKE_COMMENT:
|
||||||
dbHelper.setCommentLiked(storyId, commentUserId, feedId, false);
|
dbHelper.setCommentLiked(storyId, commentUserId, userId, false);
|
||||||
impact |= UPDATE_SOCIAL;
|
impact |= UPDATE_SOCIAL;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case REPLY:
|
case REPLY:
|
||||||
if (isFollowup) break; // replies are only placeholders
|
if (isFollowup) break; // replies are only placeholders
|
||||||
dbHelper.insertReplyPlaceholder(storyId, feedId, commentUserId, commentReplyText);
|
dbHelper.insertReplyPlaceholder(storyId, userId, commentUserId, commentReplyText);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EDIT_REPLY:
|
case EDIT_REPLY:
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.newsblur.util;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import static android.graphics.Bitmap.Config.ARGB_8888;
|
import static android.graphics.Bitmap.Config.ARGB_8888;
|
||||||
import static com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL;
|
import static com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL;
|
||||||
|
@ -35,6 +36,7 @@ import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.browser.customtabs.CustomTabColorSchemeParams;
|
import androidx.browser.customtabs.CustomTabColorSchemeParams;
|
||||||
|
@ -44,6 +46,7 @@ import androidx.core.content.ContextCompat;
|
||||||
import com.google.android.material.appbar.AppBarLayout;
|
import com.google.android.material.appbar.AppBarLayout;
|
||||||
import com.google.android.material.appbar.MaterialToolbar;
|
import com.google.android.material.appbar.MaterialToolbar;
|
||||||
import com.google.android.material.color.MaterialColors;
|
import com.google.android.material.color.MaterialColors;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.newsblur.NbApplication;
|
import com.newsblur.NbApplication;
|
||||||
import com.newsblur.R;
|
import com.newsblur.R;
|
||||||
import com.newsblur.activity.*;
|
import com.newsblur.activity.*;
|
||||||
|
@ -422,7 +425,7 @@ public class UIUtils {
|
||||||
* upon the provided classifier sub-type map while also setting up handlers to alter said
|
* upon the provided classifier sub-type map while also setting up handlers to alter said
|
||||||
* map if the buttons are pressed.
|
* map if the buttons are pressed.
|
||||||
*/
|
*/
|
||||||
public static void setupIntelDialogRow(final View row, final Map<String,Integer> classifier, final String key) {
|
public static void setupIntelDialogRow(final View row, @NonNull final Map<String,Integer> classifier, final String key) {
|
||||||
colourIntelDialogRow(row, classifier, key);
|
colourIntelDialogRow(row, classifier, key);
|
||||||
row.findViewById(R.id.intel_row_like).setOnClickListener(v -> {
|
row.findViewById(R.id.intel_row_like).setOnClickListener(v -> {
|
||||||
classifier.put(key, Classifier.LIKE);
|
classifier.put(key, Classifier.LIKE);
|
||||||
|
@ -433,7 +436,11 @@ public class UIUtils {
|
||||||
colourIntelDialogRow(row, classifier, key);
|
colourIntelDialogRow(row, classifier, key);
|
||||||
});
|
});
|
||||||
row.findViewById(R.id.intel_row_clear).setOnClickListener(v -> {
|
row.findViewById(R.id.intel_row_clear).setOnClickListener(v -> {
|
||||||
classifier.put(key, Classifier.CLEAR_LIKE);
|
if (Objects.equals(classifier.get(key), Classifier.DISLIKE)) {
|
||||||
|
classifier.put(key, Classifier.CLEAR_DISLIKE);
|
||||||
|
} else {
|
||||||
|
classifier.put(key, Classifier.CLEAR_LIKE);
|
||||||
|
}
|
||||||
colourIntelDialogRow(row, classifier, key);
|
colourIntelDialogRow(row, classifier, key);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -599,4 +606,15 @@ public class UIUtils {
|
||||||
context.sendBroadcast(intent);
|
context.sendBroadcast(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void showSnackBar(View view, String message) {
|
||||||
|
Snackbar.make(view, message, 600).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] getLoadingColorsArray(Context context) {
|
||||||
|
return new int[]{ContextCompat.getColor(context, R.color.refresh_1),
|
||||||
|
ContextCompat.getColor(context, R.color.refresh_2),
|
||||||
|
ContextCompat.getColor(context, R.color.refresh_3),
|
||||||
|
ContextCompat.getColor(context, R.color.refresh_4)};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.newsblur.database.BlurDatabaseHelper
|
import com.newsblur.database.BlurDatabaseHelper
|
||||||
|
import com.newsblur.util.CursorFilters
|
||||||
import com.newsblur.util.FeedSet
|
import com.newsblur.util.FeedSet
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -21,9 +22,9 @@ class StoriesViewModel
|
||||||
private val _activeStoriesLiveData = MutableLiveData<Cursor>()
|
private val _activeStoriesLiveData = MutableLiveData<Cursor>()
|
||||||
val activeStoriesLiveData: LiveData<Cursor> = _activeStoriesLiveData
|
val activeStoriesLiveData: LiveData<Cursor> = _activeStoriesLiveData
|
||||||
|
|
||||||
fun getActiveStories(fs: FeedSet) {
|
fun getActiveStories(fs: FeedSet, cursorFilters: CursorFilters) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
dbHelper.getActiveStoriesCursor(fs, cancellationSignal).let {
|
dbHelper.getActiveStoriesCursor(fs, cursorFilters, cancellationSignal).let {
|
||||||
_activeStoriesLiveData.postValue(it)
|
_activeStoriesLiveData.postValue(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,9 +149,10 @@ class WidgetRemoteViewsFactory(context: Context, intent: Intent) : RemoteViewsFa
|
||||||
Log.d(this.javaClass.name, "onDataSetChanged - get remote stories")
|
Log.d(this.javaClass.name, "onDataSetChanged - get remote stories")
|
||||||
val response = apiManager.getStories(fs, 1, StoryOrder.NEWEST, ReadFilter.ALL)
|
val response = apiManager.getStories(fs, 1, StoryOrder.NEWEST, ReadFilter.ALL)
|
||||||
response.stories?.let {
|
response.stories?.let {
|
||||||
|
val stateFilter = PrefsUtils.getStateFilter(context)
|
||||||
Log.d(this.javaClass.name, "onDataSetChanged - got ${it.size} remote stories")
|
Log.d(this.javaClass.name, "onDataSetChanged - got ${it.size} remote stories")
|
||||||
processStories(response.stories)
|
processStories(response.stories)
|
||||||
dbHelper.insertStories(response, true)
|
dbHelper.insertStories(response, stateFilter, true)
|
||||||
} ?: Log.d(this.javaClass.name, "onDataSetChanged - null remote stories")
|
} ?: Log.d(this.javaClass.name, "onDataSetChanged - null remote stories")
|
||||||
}
|
}
|
||||||
} catch (e: TimeoutCancellationException) {
|
} catch (e: TimeoutCancellationException) {
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
<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="@color/gray55"
|
|
||||||
android:pathData="M19,18l2,1V3c0,-1.1 -0.9,-2 -2,-2H8.99C7.89,1 7,1.9 7,3h10c1.1,0 2,0.9 2,2v13zM15,5H5c-1.1,0 -2,0.9 -2,2v16l7,-3 7,3V7c0,-1.1 -0.9,-2 -2,-2z" />
|
|
||||||
</vector>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<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="@color/gray55"
|
|
||||||
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z" />
|
|
||||||
</vector>
|
|
|
@ -1,12 +0,0 @@
|
||||||
<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="@color/gray55"
|
|
||||||
android:pathData="M6.18,17.82m-2.18,0a2.18,2.18 0,1 1,4.36 0a2.18,2.18 0,1 1,-4.36 0" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/gray55"
|
|
||||||
android:pathData="M4,4.44v2.83c7.03,0 12.73,5.7 12.73,12.73h2.83c0,-8.59 -6.97,-15.56 -15.56,-15.56zM4,10.1v2.83c3.9,0 7.07,3.17 7.07,7.07h2.83c0,-5.47 -4.43,-9.9 -9.9,-9.9z" />
|
|
||||||
</vector>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<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="@color/gray55"
|
|
||||||
android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z" />
|
|
||||||
</vector>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<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="@color/gray55"
|
|
||||||
android:pathData="M14,17L4,17v2h10v-2zM20,9L4,9v2h16L20,9zM4,15h16v-2L4,13v2zM4,5v2h16L20,5L4,5z" />
|
|
||||||
</vector>
|
|
|
@ -1,10 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:animateLayoutChanges="true" >
|
android:animateLayoutChanges="true"
|
||||||
|
android:descendantFocusability="blocksDescendants">
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:id="@+id/main_top_bar"
|
android:id="@+id/main_top_bar"
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
to be defined first so that other things can be placed above it. -->
|
to be defined first so that other things can be placed above it. -->
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/fragment_feedintelligenceselector"
|
android:id="@+id/fragment_feedintelligenceselector"
|
||||||
android:name="com.newsblur.fragment.FeedIntelligenceSelectorFragment"
|
android:name="com.newsblur.fragment.FeedSelectorFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
|
@ -175,7 +175,7 @@
|
||||||
android:imeOptions="actionSearch"
|
android:imeOptions="actionSearch"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:animateLayoutChanges="true"
|
android:animateLayoutChanges="true"
|
||||||
/>
|
android:importantForAutofill="no" />
|
||||||
|
|
||||||
<!-- The scrollable and pull-able feed list. -->
|
<!-- The scrollable and pull-able feed list. -->
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:descendantFocusability="blocksDescendants">
|
||||||
|
|
||||||
<include layout="@layout/toolbar_newsblur" />
|
<include layout="@layout/toolbar_newsblur" />
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,8 @@
|
||||||
android:hint="@string/share_comment_hint"
|
android:hint="@string/share_comment_hint"
|
||||||
android:inputType="textCapSentences|textMultiLine"
|
android:inputType="textCapSentences|textMultiLine"
|
||||||
android:singleLine="false"
|
android:singleLine="false"
|
||||||
android:textSize="15sp" />
|
android:textSize="15sp"
|
||||||
|
android:importantForAutofill="no" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:id="@+id/container_buttons"
|
android:id="@+id/container_buttons"
|
||||||
|
|
|
@ -6,17 +6,33 @@
|
||||||
android:animateLayoutChanges="true"
|
android:animateLayoutChanges="true"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:id="@+id/text_sync_status"
|
android:id="@+id/container_sync_status"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/status_overlay_background"
|
android:background="@color/status_overlay_background"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:padding="2dp"
|
android:orientation="horizontal"
|
||||||
android:text="@string/sync_status_feed_add"
|
android:paddingVertical="1dp"
|
||||||
android:textColor="@color/status_overlay_text"
|
android:visibility="gone">
|
||||||
android:textSize="14sp"
|
|
||||||
android:visibility="gone" />
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:text="@string/sync_status_feed_add"
|
||||||
|
android:textColor="@color/status_overlay_text"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
style="?circleProgressIndicator"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:indeterminate="true" />
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_add_folder_title"
|
android:id="@+id/text_add_folder_title"
|
||||||
|
@ -45,10 +61,10 @@
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:autofillHints="@null"
|
android:autofillHints="@null"
|
||||||
android:textSize="14sp"
|
|
||||||
android:hint="@string/new_folder_name_hint"
|
android:hint="@string/new_folder_name_hint"
|
||||||
android:inputType="textCapSentences"
|
android:inputType="textCapSentences"
|
||||||
android:maxLines="1" />
|
android:maxLines="1"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/ic_create_folder"
|
android:id="@+id/ic_create_folder"
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="25dp"
|
||||||
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/feeds_shortcuts"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:text="@string/short_open_all_stories"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_open_all_stories_key"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:text="@string/short_switch_views"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_switch_views_key_left"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:text="@string/short_switch_views"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_switch_views_key_right"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:text="@string/short_add_site"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_add_site_key"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
</ScrollView>
|
|
@ -24,6 +24,7 @@
|
||||||
android:id="@+id/login_username"
|
android:id="@+id/login_username"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints="username"
|
||||||
android:hint="@string/login_username_hint"
|
android:hint="@string/login_username_hint"
|
||||||
android:inputType="textEmailAddress"
|
android:inputType="textEmailAddress"
|
||||||
android:textSize="22sp" />
|
android:textSize="22sp" />
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="30dp"
|
android:layout_marginTop="30dp"
|
||||||
|
android:autofillHints="password"
|
||||||
android:hint="@string/login_password_hint"
|
android:hint="@string/login_password_hint"
|
||||||
android:imeOptions="actionDone"
|
android:imeOptions="actionDone"
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
|
@ -103,7 +105,8 @@
|
||||||
android:hint="@string/login_custom_server_hint"
|
android:hint="@string/login_custom_server_hint"
|
||||||
android:inputType="textNoSuggestions|textMultiLine"
|
android:inputType="textNoSuggestions|textMultiLine"
|
||||||
android:textSize="17sp"
|
android:textSize="17sp"
|
||||||
android:visibility="invisible" />
|
android:visibility="invisible"
|
||||||
|
android:importantForAutofill="no" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/button_reset_url"
|
android:id="@+id/button_reset_url"
|
||||||
|
@ -165,6 +168,7 @@
|
||||||
android:id="@+id/registration_username"
|
android:id="@+id/registration_username"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints="username"
|
||||||
android:hint="@string/login_username_hint"
|
android:hint="@string/login_username_hint"
|
||||||
android:inputType="textEmailAddress"
|
android:inputType="textEmailAddress"
|
||||||
android:textSize="22sp">
|
android:textSize="22sp">
|
||||||
|
@ -176,6 +180,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="30dp"
|
android:layout_marginTop="30dp"
|
||||||
|
android:autofillHints="password"
|
||||||
android:hint="@string/login_password_hint"
|
android:hint="@string/login_password_hint"
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:nextFocusDown="@+id/registration_email"
|
android:nextFocusDown="@+id/registration_email"
|
||||||
|
@ -186,6 +191,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="30dp"
|
android:layout_marginTop="30dp"
|
||||||
|
android:autofillHints="emailAddress"
|
||||||
android:hint="@string/login_registration_email_hint"
|
android:hint="@string/login_registration_email_hint"
|
||||||
android:imeOptions="actionDone"
|
android:imeOptions="actionDone"
|
||||||
android:inputType="textEmailAddress"
|
android:inputType="textEmailAddress"
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
android:layout_marginBottom="10dp"
|
android:layout_marginBottom="10dp"
|
||||||
android:layout_marginTop="5dp"
|
android:layout_marginTop="5dp"
|
||||||
android:singleLine="false"
|
android:singleLine="false"
|
||||||
|
android:autofillHints="username"
|
||||||
android:inputType="textCapSentences|textMultiLine" />
|
android:inputType="textCapSentences|textMultiLine" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
android:layout_marginBottom="10dp"
|
android:layout_marginBottom="10dp"
|
||||||
android:layout_marginTop="5dp"
|
android:layout_marginTop="5dp"
|
||||||
android:singleLine="false"
|
android:singleLine="false"
|
||||||
android:inputType="textCapSentences|textMultiLine" />
|
android:inputType="textCapSentences|textMultiLine"
|
||||||
|
android:importantForAutofill="no" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
android:layout_marginTop="5dp"
|
android:layout_marginTop="5dp"
|
||||||
android:singleLine="false"
|
android:singleLine="false"
|
||||||
android:inputType="textCapSentences|textMultiLine"
|
android:inputType="textCapSentences|textMultiLine"
|
||||||
android:hint="@string/share_comment_hint"/>
|
android:hint="@string/share_comment_hint"
|
||||||
|
android:importantForAutofill="no" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
|
@ -0,0 +1,397 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="25dp"
|
||||||
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/stories_shortcuts"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:text="@string/short_next_story"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_next_story_key_j"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:text="@string/short_next_story"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_next_story_key_down"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:text="@string/short_previous_story"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_previous_story_key_k"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:text="@string/short_previous_story"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_previous_story_key_up"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="7dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:text="@string/short_text_view"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_text_view_key"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:text="@string/short_page_down"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_page_down_key"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:text="@string/short_page_up"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/txt_page_up_key"
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_page_up_key"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:text="@string/short_next_unread_story"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_next_unread_story_key"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:text="@string/short_toggle_read_unread"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_toggle_read_unread_key_u"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:text="@string/short_toggle_read_unread"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_toggle_read_unread_key_m"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:text="@string/short_save_unsave_story"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_save_unsave_story_key"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:text="@string/short_open_in_browser"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_open_in_browser_key_o"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:text="@string/short_open_in_browser"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_open_in_browser_key_v"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:text="@string/short_share_this_story"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/txt_share_story_key"
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_share_this_story_key"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:text="@string/short_scroll_to_comments"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_scroll_to_comments_key"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:text="@string/short_open_story_trainer"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="?defaultText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/short_open_story_trainer_key"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
</ScrollView>
|
|
@ -31,6 +31,11 @@
|
||||||
android:title="@string/menu_newsletters"
|
android:title="@string/menu_newsletters"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item android:id="@+id/menu_shortcuts"
|
||||||
|
android:title="@string/menu_shortcuts"
|
||||||
|
app:showAsAction="never"
|
||||||
|
android:visible="false" />
|
||||||
|
|
||||||
<item android:id="@+id/menu_text_size"
|
<item android:id="@+id/menu_text_size"
|
||||||
android:title="@string/menu_text_size" >
|
android:title="@string/menu_text_size" >
|
||||||
<menu>
|
<menu>
|
||||||
|
|
|
@ -22,6 +22,12 @@
|
||||||
app:showAsAction="never"
|
app:showAsAction="never"
|
||||||
android:title="@string/menu_send_story_full"/>
|
android:title="@string/menu_send_story_full"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_shortcuts"
|
||||||
|
android:title="@string/menu_shortcuts"
|
||||||
|
android:visible="false"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item android:id="@+id/menu_text_size"
|
<item android:id="@+id/menu_text_size"
|
||||||
android:title="@string/menu_text_size" >
|
android:title="@string/menu_text_size" >
|
||||||
<menu>
|
<menu>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<string name="newsblur">NewsBlur</string>
|
<string name="newsblur">NewsBlur</string>
|
||||||
|
|
||||||
|
@ -274,6 +274,9 @@
|
||||||
<string name="setup_instructions_details">To read your email newsletters in NewsBlur, forward your newsletters to your custom email address shown above.\n\nIn Gmail, go to Settings > Forwarding and click on Add a forwarding address. Add your custom NewsBlur email address.\n\nGmail will walk you through confirming the email address. You\'ll want to come back to NewsBlur and look for the confirmation email under the "Newsletters" folder.\n\nNext, create a filter with all of your newsletters so that they forward to the custom address on NewsBlur.</string>
|
<string name="setup_instructions_details">To read your email newsletters in NewsBlur, forward your newsletters to your custom email address shown above.\n\nIn Gmail, go to Settings > Forwarding and click on Add a forwarding address. Add your custom NewsBlur email address.\n\nGmail will walk you through confirming the email address. You\'ll want to come back to NewsBlur and look for the confirmation email under the "Newsletters" folder.\n\nNext, create a filter with all of your newsletters so that they forward to the custom address on NewsBlur.</string>
|
||||||
<string name="copy_email">Copy email</string>
|
<string name="copy_email">Copy email</string>
|
||||||
|
|
||||||
|
<string name="stories_shortcuts">Stories shortcuts</string>
|
||||||
|
<string name="feeds_shortcuts">Feeds shortcuts</string>
|
||||||
|
|
||||||
<string name="import_export">Import/Export…</string>
|
<string name="import_export">Import/Export…</string>
|
||||||
<string name="settings">Preferences…</string>
|
<string name="settings">Preferences…</string>
|
||||||
<string name="menu_mute_sites">Mute Sites…</string>
|
<string name="menu_mute_sites">Mute Sites…</string>
|
||||||
|
@ -285,6 +288,7 @@
|
||||||
<string name="title_no_subscriptions">No active subscriptions detected</string>
|
<string name="title_no_subscriptions">No active subscriptions detected</string>
|
||||||
<string name="title_widget_loading">Loading…</string>
|
<string name="title_widget_loading">Loading…</string>
|
||||||
<string name="menu_newsletters">Newsletters…</string>
|
<string name="menu_newsletters">Newsletters…</string>
|
||||||
|
<string name="menu_shortcuts">Shortcuts…</string>
|
||||||
|
|
||||||
<string name="import_export_title">Import/Export OPML</string>
|
<string name="import_export_title">Import/Export OPML</string>
|
||||||
<string name="notifications_title">Notifications</string>
|
<string name="notifications_title">Notifications</string>
|
||||||
|
@ -577,7 +581,7 @@
|
||||||
<string name="sync_status_text">Storing text for %s stories…</string>
|
<string name="sync_status_text">Storing text for %s stories…</string>
|
||||||
<string name="sync_status_images">Storing %s images…</string>
|
<string name="sync_status_images">Storing %s images…</string>
|
||||||
<string name="sync_status_offline">Offline</string>
|
<string name="sync_status_offline">Offline</string>
|
||||||
<string name="sync_status_feed_add">Adding feed …</string>
|
<string name="sync_status_feed_add">Adding feed</string>
|
||||||
|
|
||||||
<string name="volume_key_navigation">Volume key navigation…</string>
|
<string name="volume_key_navigation">Volume key navigation…</string>
|
||||||
<string name="off">Off</string>
|
<string name="off">Off</string>
|
||||||
|
@ -739,4 +743,64 @@
|
||||||
|
|
||||||
<string name="notification_permissions_context">Permissions is required for posting notifications</string>
|
<string name="notification_permissions_context">Permissions is required for posting notifications</string>
|
||||||
<string name="notification_permissions_rationale">Notifications permission must be added manually in the app\'s settings before trying again to enable notifications</string>
|
<string name="notification_permissions_rationale">Notifications permission must be added manually in the app\'s settings before trying again to enable notifications</string>
|
||||||
|
|
||||||
|
<string name="story_saved">Story marked as saved</string>
|
||||||
|
<string name="story_unsaved">Story marked as unsaved</string>
|
||||||
|
<string name="story_read">Story marked as read</string>
|
||||||
|
<string name="story_unread">Story marked as unread</string>
|
||||||
|
|
||||||
|
<string name="unread_stories">Unread stories</string>
|
||||||
|
<string name="focused_stories">Focused stories</string>
|
||||||
|
<string name="saved_stories">Saved stories</string>
|
||||||
|
|
||||||
|
<string name="short_next_story">Next Story</string>
|
||||||
|
<string name="short_next_story_key_down">\u2193</string>
|
||||||
|
<string name="short_next_story_key_j">J</string>
|
||||||
|
|
||||||
|
<string name="short_previous_story">Previous Story</string>
|
||||||
|
<string name="short_previous_story_key_up">\u2191</string>
|
||||||
|
<string name="short_previous_story_key_k">K</string>
|
||||||
|
|
||||||
|
<string name="short_text_view">Text View</string>
|
||||||
|
<string name="short_text_view_key">\u21E7 \u23CE</string>
|
||||||
|
|
||||||
|
<string name="short_page_down">Page Down</string>
|
||||||
|
<string name="short_page_down_key">space</string>
|
||||||
|
|
||||||
|
<string name="short_page_up">Page Up</string>
|
||||||
|
<string name="short_page_up_key">\u21E7 space</string>
|
||||||
|
|
||||||
|
<string name="short_next_unread_story">Next Unread Story</string>
|
||||||
|
<string name="short_next_unread_story_key">N</string>
|
||||||
|
|
||||||
|
<string name="short_toggle_read_unread">Toggle Read/Unread</string>
|
||||||
|
<string name="short_toggle_read_unread_key_u">U</string>
|
||||||
|
<string name="short_toggle_read_unread_key_m">M</string>
|
||||||
|
|
||||||
|
<string name="short_save_unsave_story">Save/Unsave Story</string>
|
||||||
|
<string name="short_save_unsave_story_key">S</string>
|
||||||
|
|
||||||
|
<string name="short_open_in_browser">Open in Browser</string>
|
||||||
|
<string name="short_open_in_browser_key_o">O</string>
|
||||||
|
<string name="short_open_in_browser_key_v">V</string>
|
||||||
|
|
||||||
|
<string name="short_share_this_story">Share this Story</string>
|
||||||
|
<string name="short_share_this_story_key">\u21E7 S</string>
|
||||||
|
|
||||||
|
<string name="short_scroll_to_comments">Scroll to Comments</string>
|
||||||
|
<string name="short_scroll_to_comments_key">C</string>
|
||||||
|
|
||||||
|
<string name="short_open_story_trainer">Open Story Trainer</string>
|
||||||
|
<string name="short_open_story_trainer_key">T</string>
|
||||||
|
|
||||||
|
<string name="short_open_all_stories">Open All Stories</string>
|
||||||
|
<string name="short_open_all_stories_key">\u2325 E</string>
|
||||||
|
|
||||||
|
<string name="short_switch_views">Switch Views</string>
|
||||||
|
<string name="short_switch_views_key_left">\u2190</string>
|
||||||
|
<string name="short_switch_views_key_right">\u2192</string>
|
||||||
|
|
||||||
|
<string name="short_add_site">Add Site</string>
|
||||||
|
<string name="short_add_site_key">\u2325 A</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
<item name="android:background">@color/bar_background</item>
|
<item name="android:background">@color/bar_background</item>
|
||||||
<item name="android:textColor">@color/gray30</item>
|
<item name="android:textColor">@color/gray30</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="subscriptionHeader.dark">
|
<style name="subscriptionHeader.dark">
|
||||||
<item name="android:background">@color/dark_bar_background</item>
|
<item name="android:background">@color/dark_bar_background</item>
|
||||||
<item name="android:textColor">@color/gray55</item>
|
<item name="android:textColor">@color/gray55</item>
|
||||||
|
@ -54,6 +55,7 @@
|
||||||
<style name="subscriptionIcon">
|
<style name="subscriptionIcon">
|
||||||
<item name="tint">@color/gray30</item>
|
<item name="tint">@color/gray30</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="subscriptionIcon.dark">
|
<style name="subscriptionIcon.dark">
|
||||||
<item name="tint">@color/gray55</item>
|
<item name="tint">@color/gray55</item>
|
||||||
</style>
|
</style>
|
||||||
|
@ -540,4 +542,24 @@
|
||||||
<item name="android:layout_height">40dp</item>
|
<item name="android:layout_height">40dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="materialSnackBarTextView" parent="@style/Widget.MaterialComponents.Snackbar.TextView">
|
||||||
|
<item name="android:textStyle">bold</item>
|
||||||
|
<item name="android:gravity">center</item>
|
||||||
|
<item name="android:textColor">@color/text</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="materialSnackBarTextView.dark" parent="@style/Widget.MaterialComponents.Snackbar.TextView">
|
||||||
|
<item name="android:textStyle">bold</item>
|
||||||
|
<item name="android:gravity">center</item>
|
||||||
|
<item name="android:textColor">@color/white</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="materialSnackBarTheme" parent="@style/Widget.MaterialComponents.Snackbar">
|
||||||
|
<item name="android:background">@color/nb_green_gray91</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="materialSnackBarTheme.dark" parent="@style/Widget.MaterialComponents.Snackbar">
|
||||||
|
<item name="android:background">@color/gray13</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -61,6 +61,8 @@
|
||||||
<item name="fontFamily">@font/whitney</item>
|
<item name="fontFamily">@font/whitney</item>
|
||||||
<item name="circleProgressIndicator">@style/circleProgressIndicator</item>
|
<item name="circleProgressIndicator">@style/circleProgressIndicator</item>
|
||||||
<item name="toggleButton">@style/toggleButton</item>
|
<item name="toggleButton">@style/toggleButton</item>
|
||||||
|
<item name="snackbarStyle">@style/materialSnackBarTheme</item>
|
||||||
|
<item name="snackbarTextViewStyle">@style/materialSnackBarTextView</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="NewsBlurDarkTheme" parent="Theme.MaterialComponents.NoActionBar">
|
<style name="NewsBlurDarkTheme" parent="Theme.MaterialComponents.NoActionBar">
|
||||||
|
@ -124,6 +126,8 @@
|
||||||
<item name="fontFamily">@font/whitney</item>
|
<item name="fontFamily">@font/whitney</item>
|
||||||
<item name="circleProgressIndicator">@style/circleProgressIndicator</item>
|
<item name="circleProgressIndicator">@style/circleProgressIndicator</item>
|
||||||
<item name="toggleButton">@style/toggleButton.dark</item>
|
<item name="toggleButton">@style/toggleButton.dark</item>
|
||||||
|
<item name="snackbarStyle">@style/materialSnackBarTheme.dark</item>
|
||||||
|
<item name="snackbarTextViewStyle">@style/materialSnackBarTextView.dark</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="NewsBlurBlackTheme" parent="Theme.MaterialComponents.NoActionBar" >
|
<style name="NewsBlurBlackTheme" parent="Theme.MaterialComponents.NoActionBar" >
|
||||||
|
@ -187,6 +191,8 @@
|
||||||
<item name="fontFamily">@font/whitney</item>
|
<item name="fontFamily">@font/whitney</item>
|
||||||
<item name="circleProgressIndicator">@style/circleProgressIndicator</item>
|
<item name="circleProgressIndicator">@style/circleProgressIndicator</item>
|
||||||
<item name="toggleButton">@style/toggleButton.dark</item>
|
<item name="toggleButton">@style/toggleButton.dark</item>
|
||||||
|
<item name="snackbarStyle">@style/materialSnackBarTheme.dark</item>
|
||||||
|
<item name="snackbarTextViewStyle">@style/materialSnackBarTextView.dark</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="NewsBlurTheme.Translucent" parent="NewsBlurTheme">
|
<style name="NewsBlurTheme.Translucent" parent="NewsBlurTheme">
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package com.newsblur
|
||||||
|
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import com.newsblur.keyboard.KeyboardEvent
|
||||||
|
import com.newsblur.keyboard.KeyboardListener
|
||||||
|
import com.newsblur.keyboard.KeyboardManager
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class HomeKeyboardShortcutsTest {
|
||||||
|
|
||||||
|
private val manager = KeyboardManager()
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun afterTest() {
|
||||||
|
manager.removeListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun openAllStoriesTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val keyEvent = mockk<KeyEvent>()
|
||||||
|
every { keyEvent.isAltPressed } returns true
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_E, keyEvent)
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.OpenAllStories) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun notOpenAllStoriesTest() {
|
||||||
|
val keyEvent = mockk<KeyEvent>()
|
||||||
|
every { keyEvent.isAltPressed } returns false
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_E, keyEvent)
|
||||||
|
Assert.assertFalse(handled)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addSiteTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val keyEvent = mockk<KeyEvent>()
|
||||||
|
every { keyEvent.isAltPressed } returns true
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_A, keyEvent)
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.AddFeed) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun notAddSiteTest() {
|
||||||
|
val keyEvent = mockk<KeyEvent>()
|
||||||
|
every { keyEvent.isAltPressed } returns false
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_A, keyEvent)
|
||||||
|
Assert.assertFalse(handled)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun switchViewLeftTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_DPAD_LEFT, mockk())
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.SwitchViewLeft) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun switchViewRightTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_DPAD_RIGHT, mockk())
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.SwitchViewRight) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
package com.newsblur
|
||||||
|
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import com.newsblur.keyboard.KeyboardEvent
|
||||||
|
import com.newsblur.keyboard.KeyboardListener
|
||||||
|
import com.newsblur.keyboard.KeyboardManager
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ReadingKeyboardShortcutsTest {
|
||||||
|
|
||||||
|
private val manager = KeyboardManager()
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun afterTest() {
|
||||||
|
manager.removeListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun previousStoryTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_J, mockk())
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.PreviousStory) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun previousStoryArrowTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_DPAD_DOWN, mockk())
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.PreviousStory) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun nextStoryTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_K, mockk())
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.NextStory) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun nextStoryArrowTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_DPAD_UP, mockk())
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.NextStory) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun toggleTextViewTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val keyEvent = mockk<KeyEvent>()
|
||||||
|
every { keyEvent.isShiftPressed } returns true
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_ENTER, keyEvent)
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.ToggleTextView) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun noToggleTextViewTest() {
|
||||||
|
val keyEvent = mockk<KeyEvent>()
|
||||||
|
every { keyEvent.isShiftPressed } returns false
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_ENTER, keyEvent)
|
||||||
|
Assert.assertFalse(handled)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun pageDownTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val keyEvent = mockk<KeyEvent>()
|
||||||
|
every { keyEvent.isShiftPressed } returns false
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_SPACE, keyEvent)
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.PageDown) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun pageUpTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val keyEvent = mockk<KeyEvent>()
|
||||||
|
every { keyEvent.isShiftPressed } returns true
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_SPACE, keyEvent)
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.PageUp) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun nextUnreadStoryTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_N, mockk())
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.NextUnreadStory) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun toggleReadUnreadUTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_U, mockk())
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.ToggleReadUnread) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun toggleReadUnreadMTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_M, mockk())
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.ToggleReadUnread) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun saveUnsaveStoryTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val keyEvent = mockk<KeyEvent>()
|
||||||
|
every { keyEvent.isShiftPressed } returns false
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_S, keyEvent)
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.SaveUnsaveStory) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shareStoryTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val keyEvent = mockk<KeyEvent>()
|
||||||
|
every { keyEvent.isShiftPressed } returns true
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_S, keyEvent)
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.ShareStory) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun openInBrowserOTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_O, mockk())
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.OpenInBrowser) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun openInBrowserVTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_V, mockk())
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.OpenInBrowser) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scrollToCommentsTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_C, mockk())
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.ScrollToComments) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun openStoryTrainerTest() {
|
||||||
|
val listener = mockk<KeyboardListener>()
|
||||||
|
every { listener.onKeyboardEvent(any()) } returns Unit
|
||||||
|
manager.addListener(listener)
|
||||||
|
|
||||||
|
val handled = manager.onKeyUp(KeyEvent.KEYCODE_T, mockk())
|
||||||
|
Assert.assertTrue(handled)
|
||||||
|
verify { listener.onKeyboardEvent(KeyboardEvent.OpenStoryTrainer) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +0,0 @@
|
||||||
plugins {
|
|
||||||
id 'com.android.application' version '7.4.0' apply false
|
|
||||||
id 'com.android.library' version '7.4.0' apply false
|
|
||||||
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
|
|
||||||
id 'org.jetbrains.kotlin.kapt' version '1.7.20' apply false
|
|
||||||
id 'com.google.dagger.hilt.android' version '2.44.2' apply false
|
|
||||||
id 'com.android.test' version '7.4.0' apply false
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
maven {
|
|
||||||
url 'https://maven.google.com'
|
|
||||||
}
|
|
||||||
google()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task clear(type: Delete) {
|
|
||||||
delete rootProject.buildDir
|
|
||||||
}
|
|
19
clients/android/NewsBlur/build.gradle.kts
Normal file
19
clients/android/NewsBlur/build.gradle.kts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
plugins {
|
||||||
|
id(Plugins.androidApplication) version Version.android apply false
|
||||||
|
id(Plugins.androidLibrary) version Version.android apply false
|
||||||
|
kotlin(Plugins.kotlinAndroid) version Version.kotlin apply false
|
||||||
|
kotlin(Plugins.kotlinKapt) version Version.kotlin apply false
|
||||||
|
id(Plugins.hiltAndroid) version Version.hilt apply false
|
||||||
|
id(Plugins.androidTest) version Version.android apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("clean", Delete::class) {
|
||||||
|
delete(rootProject.buildDir)
|
||||||
|
}
|
9
clients/android/NewsBlur/buildSrc/build.gradle.kts
Normal file
9
clients/android/NewsBlur/buildSrc/build.gradle.kts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
plugins {
|
||||||
|
`kotlin-dsl`
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
14
clients/android/NewsBlur/buildSrc/src/main/java/Config.kt
Normal file
14
clients/android/NewsBlur/buildSrc/src/main/java/Config.kt
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import org.gradle.api.JavaVersion
|
||||||
|
|
||||||
|
object Config {
|
||||||
|
|
||||||
|
const val compileSdk = 33
|
||||||
|
const val minSdk = 23
|
||||||
|
const val targetSdk = 33
|
||||||
|
const val versionCode = 213
|
||||||
|
const val versionName = "13.1.0"
|
||||||
|
|
||||||
|
const val androidTestInstrumentation = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
val javaVersion = JavaVersion.VERSION_17
|
||||||
|
}
|
15
clients/android/NewsBlur/buildSrc/src/main/java/Const.kt
Normal file
15
clients/android/NewsBlur/buildSrc/src/main/java/Const.kt
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
object Const {
|
||||||
|
|
||||||
|
const val namespace = "com.newsblur"
|
||||||
|
const val namespaceBenchmark = "com.newsblur.benchmark"
|
||||||
|
|
||||||
|
const val release = "release"
|
||||||
|
const val debug = "debug"
|
||||||
|
const val benchmark = "benchmark"
|
||||||
|
|
||||||
|
const val selfInstrumenting = "android.experimental.self-instrumenting"
|
||||||
|
|
||||||
|
const val benchmarkProguard = "benchmark-rules.pro"
|
||||||
|
const val appProguard = "proguard-rules.pro"
|
||||||
|
const val defaultProguard = "proguard-android.txt"
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
object Dependencies {
|
||||||
|
|
||||||
|
const val fragment = "androidx.fragment:fragment-ktx:${Version.fragment}"
|
||||||
|
const val recyclerView = "androidx.recyclerview:recyclerview:${Version.recyclerView}"
|
||||||
|
const val swipeRefreshLayout = "androidx.swiperefreshlayout:swiperefreshlayout:${Version.swipeRefreshLayout}"
|
||||||
|
const val okHttp = "com.squareup.okhttp3:okhttp:${Version.okHttp}"
|
||||||
|
const val gson = "com.google.code.gson:gson:${Version.gson}"
|
||||||
|
const val billing = "com.android.billingclient:billing:${Version.billing}"
|
||||||
|
const val playCore = "com.google.android.play:core:${Version.playCore}"
|
||||||
|
const val material = "com.google.android.material:material:${Version.material}"
|
||||||
|
const val preference = "androidx.preference:preference-ktx:${Version.preference}"
|
||||||
|
const val browser = "androidx.browser:browser:${Version.browser}"
|
||||||
|
const val lifecycleRuntime = "androidx.lifecycle:lifecycle-runtime-ktx:${Version.lifecycle}"
|
||||||
|
const val lifecycleProcess = "androidx.lifecycle:lifecycle-process:${Version.lifecycle}"
|
||||||
|
const val splashScreen = "androidx.core:core-splashscreen:${Version.splashScreen}"
|
||||||
|
const val hiltAndroid = "com.google.dagger:hilt-android:${Version.hilt}"
|
||||||
|
const val hiltCompiler = "com.google.dagger:hilt-compiler:${Version.hilt}"
|
||||||
|
const val profileInstaller = "androidx.profileinstaller:profileinstaller:${Version.profileInstaller}"
|
||||||
|
|
||||||
|
// test
|
||||||
|
const val junit = "junit:junit:${Version.junit}"
|
||||||
|
const val mockk = "io.mockk:mockk:${Version.mockk}"
|
||||||
|
|
||||||
|
// android test
|
||||||
|
const val junitExt = "androidx.test.ext:junit:${Version.junitExt}"
|
||||||
|
const val espressoCore = "androidx.test.espresso:espresso-core:${Version.espresso}"
|
||||||
|
|
||||||
|
const val uiAutomator = "androidx.test.uiautomator:uiautomator:${Version.uiAutomator}"
|
||||||
|
const val benchmarkMacroJunit4 = "androidx.benchmark:benchmark-macro-junit4:${Version.benchmarkMacroJunit4}"
|
||||||
|
}
|
16
clients/android/NewsBlur/buildSrc/src/main/java/Plugins.kt
Normal file
16
clients/android/NewsBlur/buildSrc/src/main/java/Plugins.kt
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
object Plugins {
|
||||||
|
|
||||||
|
const val kotlinAndroid = "android"
|
||||||
|
const val kotlinKapt = "kapt"
|
||||||
|
const val androidApplication = "com.android.application"
|
||||||
|
const val androidLibrary = "com.android.library"
|
||||||
|
const val hiltAndroid = "com.google.dagger.hilt.android"
|
||||||
|
const val androidTest = "com.android.test"
|
||||||
|
|
||||||
|
// id("com.android.application") version "8.1.0" apply false
|
||||||
|
// id("com.android.library") version "8.1.0" apply false
|
||||||
|
// id("org.jetbrains.kotlin.android") version "1.8.10" apply false
|
||||||
|
// id("org.jetbrains.kotlin.kapt") version "1.8.10" apply false
|
||||||
|
// id("com.google.dagger.hilt.android") version "2.44.2" apply false
|
||||||
|
// id("com.android.test") version "8.1.0" apply false
|
||||||
|
}
|
33
clients/android/NewsBlur/buildSrc/src/main/java/Version.kt
Normal file
33
clients/android/NewsBlur/buildSrc/src/main/java/Version.kt
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
object Version {
|
||||||
|
|
||||||
|
const val android = "8.1.0"
|
||||||
|
|
||||||
|
const val kotlin = "1.8.10"
|
||||||
|
|
||||||
|
const val fragment = "1.6.1"
|
||||||
|
const val recyclerView = "1.3.1"
|
||||||
|
const val swipeRefreshLayout = "1.1.0"
|
||||||
|
|
||||||
|
const val okHttp = "4.11.0"
|
||||||
|
const val gson = "2.10"
|
||||||
|
const val billing = "6.0.1"
|
||||||
|
const val playCore = "1.10.3"
|
||||||
|
const val material = "1.9.0"
|
||||||
|
|
||||||
|
const val preference = "1.2.0"
|
||||||
|
const val browser = "1.5.0"
|
||||||
|
const val lifecycle = "2.6.1"
|
||||||
|
const val splashScreen = "1.0.1"
|
||||||
|
|
||||||
|
const val hilt = "2.44.2"
|
||||||
|
const val profileInstaller = "1.3.1"
|
||||||
|
|
||||||
|
const val junit = "4.13.2"
|
||||||
|
const val mockk = "1.13.4"
|
||||||
|
|
||||||
|
const val junitExt = "1.1.5"
|
||||||
|
const val espresso = "3.5.1"
|
||||||
|
|
||||||
|
const val uiAutomator = "2.2.0"
|
||||||
|
const val benchmarkMacroJunit4 = "1.1.1"
|
||||||
|
}
|
|
@ -1,3 +1,6 @@
|
||||||
org.gradle.jvmargs=-Xmx1536M -Dkotlin.daemon.jvm.options\="-Xmx1536M"
|
org.gradle.jvmargs=-Xmx1536M -Dkotlin.daemon.jvm.options\="-Xmx1536M"
|
||||||
kotlin.code.style=obsolete
|
kotlin.code.style=obsolete
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
|
android.nonTransitiveRClass=true
|
||||||
|
android.nonFinalResIds=true
|
|
@ -1,6 +1,6 @@
|
||||||
#Thu Oct 27 16:20:00 PDT 2022
|
#Thu Oct 27 16:20:00 PDT 2022
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
@ -6,5 +6,5 @@ pluginManagement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rootProject.name = "NewsBlur"
|
rootProject.name = "NewsBlur"
|
||||||
include ':app'
|
include("app")
|
||||||
include ':app:benchmark'
|
include("app:benchmark")
|
Loading…
Add table
Reference in a new issue