aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml43
-rw-r--r--Dockerfile49
-rw-r--r--README.md2
-rw-r--r--_layouts/default.html68
-rw-r--r--app/build.gradle17
-rw-r--r--app/src/androidTest/kotlin/com/pitchedapps/frost/db/BaseDbTest.kt48
-rw-r--r--app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt48
-rw-r--r--app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt85
-rw-r--r--app/src/androidTest/kotlin/com/pitchedapps/frost/db/DatabaseTest.kt54
-rw-r--r--app/src/androidTest/kotlin/com/pitchedapps/frost/db/GenericDbTest.kt62
-rw-r--r--app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt145
-rw-r--r--app/src/main/AndroidManifest.xml14
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt11
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt45
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt134
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt31
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt20
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt40
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt4
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt86
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt90
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/db/DaoUtils.kt28
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt106
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt (renamed from app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt)24
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt71
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt202
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt92
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/dbflow/DbUtils.kt39
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/dbflow/NotificationDb.kt72
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt35
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/FrostParser.kt16
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/MessageParser.kt11
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/NotifParser.kt11
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/SearchParser.kt5
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragmentBase.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt97
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt16
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt9
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt7
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt5
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/TimeUtils.kt50
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt12
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt4
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt9
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt12
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/DebugWebView.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt4
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt16
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt194
-rw-r--r--app/src/main/res/drawable/notification_widget_preview.xml48
-rw-r--r--app/src/main/res/layout/widget_notification_item.xml46
-rw-r--r--app/src/main/res/layout/widget_notifications.xml31
-rw-r--r--app/src/main/res/values-cs-rCZ/strings.xml8
-rw-r--r--app/src/main/res/values-da-rDK/strings.xml8
-rw-r--r--app/src/main/res/values-de-rDE/strings.xml9
-rw-r--r--app/src/main/res/values-de-rDE/strings_pref_feed.xml2
-rw-r--r--app/src/main/res/values-es-rES/strings.xml11
-rw-r--r--app/src/main/res/values-es-rES/strings_pref_behaviour.xml2
-rw-r--r--app/src/main/res/values-es-rES/strings_pref_feed.xml2
-rw-r--r--app/src/main/res/values-es/strings_pref_feed.xml15
-rw-r--r--app/src/main/res/values-fr-rFR/strings.xml12
-rw-r--r--app/src/main/res/values-fr-rFR/strings_pref_behaviour.xml2
-rw-r--r--app/src/main/res/values-fr-rFR/strings_pref_feed.xml4
-rw-r--r--app/src/main/res/values-gl-rES/strings.xml8
-rw-r--r--app/src/main/res/values-hu-rHU/strings.xml9
-rw-r--r--app/src/main/res/values-hu-rHU/strings_pref_feed.xml2
-rw-r--r--app/src/main/res/values-in-rID/strings.xml8
-rw-r--r--app/src/main/res/values-it-rIT/strings.xml8
-rw-r--r--app/src/main/res/values-it/strings_pref_feed.xml15
-rw-r--r--app/src/main/res/values-ko-rKR/strings.xml8
-rw-r--r--app/src/main/res/values-nl-rNL/strings.xml8
-rw-r--r--app/src/main/res/values-no-rNO/strings.xml8
-rw-r--r--app/src/main/res/values-pl-rPL/strings.xml8
-rw-r--r--app/src/main/res/values-pt-rBR/strings.xml12
-rw-r--r--app/src/main/res/values-pt-rBR/strings_pref_behaviour.xml2
-rw-r--r--app/src/main/res/values-pt-rBR/strings_pref_feed.xml2
-rw-r--r--app/src/main/res/values-pt-rPT/strings.xml8
-rw-r--r--app/src/main/res/values-ro-rRO/strings.xml8
-rw-r--r--app/src/main/res/values-ru-rRU/strings.xml11
-rw-r--r--app/src/main/res/values-ru-rRU/strings_pref_feed.xml2
-rw-r--r--app/src/main/res/values-sr-rSP/strings.xml8
-rw-r--r--app/src/main/res/values-sv-rSE/strings.xml8
-rw-r--r--app/src/main/res/values-th-rTH/strings.xml8
-rw-r--r--app/src/main/res/values-tl-rPH/strings.xml8
-rw-r--r--app/src/main/res/values-tr-rTR/strings.xml11
-rw-r--r--app/src/main/res/values-tr-rTR/strings_pref_behaviour.xml1
-rw-r--r--app/src/main/res/values-tr-rTR/strings_pref_feed.xml2
-rw-r--r--app/src/main/res/values-uk-rUA/strings.xml9
-rw-r--r--app/src/main/res/values-uk-rUA/strings_pref_feed.xml2
-rw-r--r--app/src/main/res/values-vi-rVN/strings.xml8
-rw-r--r--app/src/main/res/values-zh-rCN/strings.xml8
-rw-r--r--app/src/main/res/values-zh-rTW/strings.xml8
-rw-r--r--app/src/main/res/values/dimens.xml2
-rw-r--r--app/src/main/res/values/strings.xml11
-rw-r--r--app/src/main/res/xml/frost_changelog.xml12
-rw-r--r--app/src/main/res/xml/notification_widget_info.xml10
-rw-r--r--app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json195
-rw-r--r--app/src/schemas/com.pitchedapps.frost.db.FrostPublicDatabase/1.json40
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/debugger/OfflineWebsiteTest.kt2
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/facebook/parsers/FbParseTest.kt8
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt5
-rw-r--r--app/src/web/.gitignore4
-rw-r--r--app/src/web/.idea/watcherTasks.xml4
-rw-r--r--app/src/web/assets/css/core/core.css307
-rw-r--r--app/src/web/assets/css/themes/custom.css339
-rw-r--r--app/src/web/assets/css/themes/material_amoled.css339
-rw-r--r--app/src/web/assets/css/themes/material_dark.css339
-rw-r--r--app/src/web/assets/css/themes/material_glass.css339
-rw-r--r--app/src/web/assets/css/themes/material_light.css339
-rw-r--r--app/src/web/assets/js/click_a.js46
-rw-r--r--app/src/web/assets/js/click_debugger.js12
-rw-r--r--app/src/web/assets/js/context_a.js98
-rw-r--r--app/src/web/assets/js/document_watcher.js23
-rw-r--r--app/src/web/assets/js/header_badges.js7
-rw-r--r--app/src/web/assets/js/media.js41
-rw-r--r--app/src/web/assets/js/menu.js55
-rw-r--r--app/src/web/assets/js/notif_msg.js25
-rw-r--r--app/src/web/assets/js/textarea_listener.js23
-rw-r--r--app/src/web/package-lock.json1630
-rw-r--r--app/src/web/package.json6
-rw-r--r--app/src/web/scss/core/_base.scss (renamed from app/src/web/assets/css/core/_base.scss)0
-rw-r--r--app/src/web/scss/core/_colors.scss (renamed from app/src/web/assets/css/core/_colors.scss)0
-rw-r--r--app/src/web/scss/core/_core_bg.scss (renamed from app/src/web/assets/css/core/_core_bg.scss)0
-rw-r--r--app/src/web/scss/core/_core_border.scss (renamed from app/src/web/assets/css/core/_core_border.scss)0
-rw-r--r--app/src/web/scss/core/_core_messenger.scss (renamed from app/src/web/assets/css/core/_core_messenger.scss)0
-rw-r--r--app/src/web/scss/core/_core_text.scss (renamed from app/src/web/assets/css/core/_core_text.scss)0
-rw-r--r--app/src/web/scss/core/_main.scss (renamed from app/src/web/assets/css/core/_main.scss)0
-rw-r--r--app/src/web/scss/core/_svg.scss (renamed from app/src/web/assets/css/core/_svg.scss)0
-rw-r--r--app/src/web/scss/core/core.scss (renamed from app/src/web/assets/css/core/core.scss)0
-rw-r--r--app/src/web/scss/themes/.gitignore (renamed from app/src/web/assets/css/themes/.gitignore)1
-rw-r--r--app/src/web/scss/themes/custom.scss (renamed from app/src/web/assets/css/themes/custom.scss)0
-rw-r--r--app/src/web/scss/themes/material_amoled.scss (renamed from app/src/web/assets/css/themes/material_amoled.scss)0
-rw-r--r--app/src/web/scss/themes/material_dark.scss (renamed from app/src/web/assets/css/themes/material_dark.scss)0
-rw-r--r--app/src/web/scss/themes/material_glass.scss (renamed from app/src/web/assets/css/themes/material_glass.scss)0
-rw-r--r--app/src/web/scss/themes/material_light.scss (renamed from app/src/web/assets/css/themes/material_light.scss)0
-rw-r--r--app/src/web/ts/click_a.ts (renamed from app/src/web/assets/js/click_a.ts)0
-rw-r--r--app/src/web/ts/click_debugger.ts (renamed from app/src/web/assets/js/click_debugger.ts)0
-rw-r--r--app/src/web/ts/context_a.ts (renamed from app/src/web/assets/js/context_a.ts)0
-rw-r--r--app/src/web/ts/document_watcher.ts (renamed from app/src/web/assets/js/document_watcher.ts)0
-rw-r--r--app/src/web/ts/header_badges.ts (renamed from app/src/web/assets/js/header_badges.ts)0
-rw-r--r--app/src/web/ts/media.ts (renamed from app/src/web/assets/js/media.ts)0
-rw-r--r--app/src/web/ts/menu.ts (renamed from app/src/web/assets/js/menu.ts)0
-rw-r--r--app/src/web/ts/notif_msg.ts (renamed from app/src/web/assets/js/notif_msg.ts)0
-rw-r--r--app/src/web/ts/textarea_listener.ts (renamed from app/src/web/assets/js/textarea_listener.ts)0
-rw-r--r--app/src/web/tsconfig.json8
-rw-r--r--app/src/web/typings/frost.d.ts (renamed from app/src/web/assets/typings/frost.d.ts)0
-rw-r--r--crowdin.yaml16
-rw-r--r--crowdin.yml5
-rwxr-xr-xdocker_build.sh12
-rw-r--r--docs/Changelog.md4
-rw-r--r--favicon/android-chrome-192x192.pngbin0 -> 2216 bytes
-rw-r--r--favicon/android-chrome-512x512.pngbin0 -> 6234 bytes
-rw-r--r--favicon/apple-touch-icon.pngbin0 -> 2133 bytes
-rw-r--r--favicon/browserconfig.xml9
-rw-r--r--favicon/favicon-16x16.pngbin0 -> 556 bytes
-rw-r--r--favicon/favicon-32x32.pngbin0 -> 667 bytes
-rw-r--r--favicon/favicon.icobin0 -> 15086 bytes
-rw-r--r--favicon/mstile-144x144.pngbin0 -> 1472 bytes
-rw-r--r--favicon/mstile-150x150.pngbin0 -> 1490 bytes
-rw-r--r--favicon/mstile-310x150.pngbin0 -> 1665 bytes
-rw-r--r--favicon/mstile-310x310.pngbin0 -> 3041 bytes
-rw-r--r--favicon/mstile-70x70.pngbin0 -> 1105 bytes
-rw-r--r--favicon/safari-pinned-tab.svg27
-rw-r--r--favicon/site.webmanifest19
-rw-r--r--files/translation_migration.sh4
-rwxr-xr-x[-rw-r--r--]generate-apk-release.sh11
-rw-r--r--gradle.properties4
169 files changed, 4200 insertions, 2842 deletions
diff --git a/.travis.yml b/.travis.yml
index b8d8f1bd..a2335bd9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,32 +1,23 @@
-language: android
-jdk:
-- oraclejdk8
-android:
- components:
- - tools
- - platform-tools
- - build-tools-28.0.3
- - android-28
- - extra-android-support
- - extra-android-m2repository
- - extra-google-m2repository
- licenses:
- - ".+"
+language: java
+services:
+- docker
git:
depth: 500
before_install:
- openssl aes-256-cbc -K $encrypted_0454d0cf846c_key -iv $encrypted_0454d0cf846c_iv
-in files/frost.tar.enc -out files/frost.tar -d
- tar xvf files/frost.tar
-- yes | sdkmanager "platforms;android-28"
+- docker build -q -t frost .
+- docker volume create -o device=$HOME/.gradle/caches/ -o o=bind gradle_caches
+- docker volume create -o device=$HOME/.gradle/wrapper/ -o o=bind gradle_wrapper
+install: true
after_success:
-- chmod +x ./generate-apk-release.sh; ./generate-apk-release.sh
+- ./generate-apk-release.sh
script:
-- cd $TRAVIS_BUILD_DIR/
-- printf "Starting script\n"
-- chmod +x gradlew
-- "./gradlew --quiet androidGitVersion"
-- "./gradlew lintReleaseTest testReleaseUnitTest assembleReleaseTest"
+- cd $TRAVIS_BUILD_DIR
+- docker run --name frost_container -v gradle_caches:/root/.gradle/caches/ -v gradle_wrapper:/root/.gradle/wrapper/ frost
+- mkdir $HOME/Frost
+- docker cp frost_container:/frost/app/build/outputs/apk/releaseTest/. $HOME/Frost
notifications:
email: false
slack:
@@ -46,14 +37,12 @@ branches:
- master
- l10n_dev
before_cache:
-- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
-- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
+ - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
+ - rm -rf $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- - "$HOME/.gradle/caches/"
- - "$HOME/.gradle/wrapper/"
- - "$HOME/.android/build-cache"
- - "$HOME/.m2s"
+ - $HOME/.gradle/caches/
+ - $HOME/.gradle/wrapper/
env:
global:
- secure: X3J97ccW+8K0bXPXhX608vPx7Pr/G4ju7quxydqMaYGgClHxoL/WpXOBAyyllde5P28PY4kioaqcI21BEhnAw0QUbmnzVLA1Qd5VS7aMPHpEnInKuOxGZ2d570OZd1f+ozFVt05vzG0VBJlBAkVhz2GWNxQdmIV1sO28MH526JMuYaEREuuywVSZmAeY7AAbW9MeCC2wvHvNmhk2nk6NLRQcsrDHcBsimy9fnnQ9lT/QsvToi1ZJd/MN7YkGDUULR+YmaotBzG546UJ1EiZQX91bFEJfP0oL43Pk7t5snzmHnKjLOr8Mt5QsIUXaiy/uzhUVmuDh1i0GEpZmhqM7nz/T6P7ogaLbbyJeauNmf15nu+e3hSvNiTzKyIwfSSflv8Do3g8/Eo3dKfIi3I8/OKF/uZ76kywh2LRqtZAqxRDiAMDZVwsRgD4aztoWm5AWa3tSoGy1J7i1eoqX6bNqokRbjgheTqcjN13kCdSZi3pZX7UBYm2Vumhn4izhTume19Rh9SqTmRgQ8jM7ynxHh7vVsJPPJG0HbQ623xz+d9mtXGy1fAb0dcUJMXdOhFN3m6AnKuHiF7cmsqje7Euk/TOZyqZmu0xEhTkugMbNKwGrklJiwRr3IoLtPdhLE38u3/auloUqBQ4K/iA9ZdhAreTSHEaI9d3J4N6kqCj3U30=
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..a8fc1e07
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,49 @@
+FROM openjdk:8
+
+# Android SDK
+
+ENV ANDROID_HOME /opt/android-sdk-linux
+
+# Download Android SDK into $ANDROID_HOME
+# You can find URL to the current version at: https://developer.android.com/studio/index.html
+# Or https://github.com/Homebrew/homebrew-cask/blob/master/Casks/android-sdk.rb
+
+RUN mkdir -p ${ANDROID_HOME} && \
+ cd ${ANDROID_HOME} && \
+ wget -q https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip -O android_tools.zip && \
+ unzip android_tools.zip && \
+ rm android_tools.zip
+
+ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools
+
+# Accept Android SDK licenses && install other elements
+# For full list; see sdkmanager --list --verbose
+RUN yes | sdkmanager --licenses && \
+ sdkmanager 'platform-tools' && \
+ sdkmanager 'extras;google;m2repository' && \
+ sdkmanager 'extras;android;m2repository'
+
+# SDK Specific
+
+RUN sdkmanager 'platforms;android-28' && \
+ sdkmanager 'build-tools;28.0.3'
+
+# Install Node.js
+
+ENV NODEJS_VERSION=11.12.0 \
+ PATH=$PATH:/opt/node/bin
+
+WORKDIR "/opt/node"
+
+RUN apt-get update && apt-get install -y curl git ca-certificates --no-install-recommends && \
+ curl -sL https://nodejs.org/dist/v${NODEJS_VERSION}/node-v${NODEJS_VERSION}-linux-x64.tar.gz | tar xz --strip-components=1 && \
+ rm -rf /var/lib/apt/lists/* && \
+ apt-get clean
+
+RUN mkdir -p /frost/
+
+WORKDIR /frost/
+
+COPY . /frost/
+
+CMD ["./docker_build.sh"] \ No newline at end of file
diff --git a/README.md b/README.md
index 30191f8f..5d2330d2 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Frost-for-Facebook
[![Releaes Version](https://img.shields.io/github/release/AllanWang/Frost-for-Facebook.svg)](https://github.com/AllanWang/Frost-for-Facebook/releases)
-[![Build Status](https://travis-ci.org/AllanWang/Frost-for-Facebook.svg?branch=dev)](https://travis-ci.org/AllanWang/Frost-for-Facebook)
+[![Build Status](https://travis-ci.com/AllanWang/Frost-for-Facebook.svg?branch=dev)](https://travis-ci.com/AllanWang/Frost-for-Facebook)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/frost-for-facebook/localized.svg)](https://crowdin.com/project/frost-for-facebook)
[![ZenHub](https://img.shields.io/badge/Shipping%20faster%20with-ZenHub-45529A.svg)](https://app.zenhub.com/workspace/o/allanwang/frost-for-facebook/boards)
[![BugSnag](https://img.shields.io/badge/Bug%20tracking%20with-BugSnag-37C2D9.svg)](https://www.bugsnag.com/)
diff --git a/_layouts/default.html b/_layouts/default.html
new file mode 100644
index 00000000..c4972e3e
--- /dev/null
+++ b/_layouts/default.html
@@ -0,0 +1,68 @@
+<!--See https://github.com/pages-themes/minimal/blob/master/_layouts/default.html-->
+<!DOCTYPE html>
+<html lang="{{ site.lang | default: "en-US" }}">
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+
+ {% seo %}
+ <link rel="stylesheet" href="{{ "/assets/css/style.css?v=" | append: site.github.build_revision | relative_url }}">
+
+ <!--Begin favicon-->
+ <link rel="apple-touch-icon" sizes="180x180" href="{{ '/favicon/apple-touch-icon.png' | relative_url }}">
+ <link rel="icon" type="image/png" sizes="32x32" href="{{ '/favicon/favicon-32x32.png' | relative_url }}">
+ <link rel="icon" type="image/png" sizes="16x16" href="{{ '/favicon/favicon-16x16.png' | relative_url }}">
+ <link rel="manifest" href="{{ '/favicon/site.webmanifest' | relative_url }}">
+ <link rel="mask-icon" href="{{ '/favicon/safari-pinned-tab.svg' | relative_url }}" color="#3b5998">
+ <link rel="shortcut icon" href="{{ '/favicon/favicon.ico' | relative_url }}">
+ <meta name="msapplication-TileColor" content="#da532c">
+ <meta name="msapplication-config" content="{{ '/favicon/browserconfig.xml' | relative_url }}">
+ <meta name="theme-color" content="#ffffff">
+ <!--End favicon-->
+ <!--[if lt IE 9]>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
+ <![endif]-->
+</head>
+<body>
+<div class="wrapper">
+ <header>
+ <h1><a href="{{ "/" | absolute_url }}">{{ site.title | default: site.github.repository_name }}</a></h1>
+
+ {% if site.logo %}
+ <img src="{{site.logo | relative_url}}" alt="Logo" />
+ {% endif %}
+
+ <p>{{ site.description | default: site.github.project_tagline }}</p>
+
+ {% if site.github.is_project_page %}
+ <p class="view"><a href="{{ site.github.repository_url }}">View the Project on GitHub <small>{{ site.github.repository_nwo }}</small></a></p>
+ {% endif %}
+
+ {% if site.github.is_user_page %}
+ <p class="view"><a href="{{ site.github.owner_url }}">View My GitHub Profile</a></p>
+ {% endif %}
+
+ {% if site.show_downloads %}
+ <ul class="downloads">
+ <li><a href="{{ site.github.zip_url }}">Download <strong>ZIP File</strong></a></li>
+ <li><a href="{{ site.github.tar_url }}">Download <strong>TAR Ball</strong></a></li>
+ <li><a href="{{ site.github.repository_url }}">View On <strong>GitHub</strong></a></li>
+ </ul>
+ {% endif %}
+ </header>
+ <section>
+
+ {{ content }}
+
+ </section>
+ <footer>
+ {% if site.github.is_project_page %}
+ <p>This project is maintained by <a href="{{ site.github.owner_url }}">{{ site.github.owner_name }}</a></p>
+ {% endif %}
+ <p><small>Hosted on GitHub Pages &mdash; Theme by <a href="https://github.com/orderedlist">orderedlist</a></small></p>
+ </footer>
+</div>
+<script src="{{ "/assets/js/scale.fix.js" | relative_url }}"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index a7f8f626..4d1ec15e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,6 +27,11 @@ android {
versionName androidGitVersion.name()
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ javaCompileOptions {
+ annotationProcessorOptions {
+ arguments = ["room.schemaLocation": "$projectDir/src/schemas".toString()]
+ }
+ }
}
applicationVariants.all { variant ->
@@ -174,6 +179,7 @@ dependencies {
androidTestImplementation kauDependency.espresso
androidTestImplementation kauDependency.testRules
androidTestImplementation kauDependency.testRunner
+ androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:${KOTLIN}"
testImplementation kauDependency.kotlinTest
testImplementation "org.jetbrains.kotlin:kotlin-reflect:${KOTLIN}"
@@ -202,9 +208,9 @@ dependencies {
implementation "androidx.biometric:biometric:${ANDX_BIOMETRIC}"
-// implementation "org.koin:koin-android:${KOIN}"
-// testImplementation "org.koin:koin-test:${KOIN}"
-// androidTestImplementation "org.koin:koin-test:${KOIN}"
+ implementation "org.koin:koin-android:${KOIN}"
+ testImplementation "org.koin:koin-test:${KOIN}"
+ androidTestImplementation "org.koin:koin-test:${KOIN}"
// androidTestImplementation "io.mockk:mockk:${MOCKK}"
@@ -255,6 +261,11 @@ dependencies {
implementation "com.sothree.slidinguppanel:library:${SLIDING_PANEL}"
+ implementation "androidx.room:room-coroutines:${ROOM}"
+ implementation "androidx.room:room-runtime:${ROOM}"
+ kapt "androidx.room:room-compiler:${ROOM}"
+ testImplementation "androidx.room:room-testing:${ROOM}"
+
}
// Validates code and generates apk
diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/BaseDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/BaseDbTest.kt
new file mode 100644
index 00000000..bae56e2f
--- /dev/null
+++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/BaseDbTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.db
+
+import android.content.Context
+import androidx.room.Room
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.runner.RunWith
+import kotlin.test.AfterTest
+import kotlin.test.BeforeTest
+
+@RunWith(AndroidJUnit4::class)
+abstract class BaseDbTest {
+
+ protected lateinit var db: FrostDatabase
+
+ @BeforeTest
+ fun before() {
+ val context = ApplicationProvider.getApplicationContext<Context>()
+ val privateDb = Room.inMemoryDatabaseBuilder(
+ context, FrostPrivateDatabase::class.java
+ ).build()
+ val publicDb = Room.inMemoryDatabaseBuilder(
+ context, FrostPublicDatabase::class.java
+ ).build()
+ db = FrostDatabase(privateDb, publicDb)
+ }
+
+ @AfterTest
+ fun after() {
+ db.close()
+ }
+}
diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt
new file mode 100644
index 00000000..417c6678
--- /dev/null
+++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.db
+
+import kotlinx.coroutines.runBlocking
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+class CacheDbTest : BaseDbTest() {
+
+ private val dao get() = db.cacheDao()
+ private val cookieDao get() = db.cookieDao()
+
+ private fun cookie(id: Long) = CookieEntity(id, "name$id", "cookie$id")
+
+ @Test
+ fun save() {
+ val cookie = cookie(1L)
+ val type = "test"
+ val content = "long test".repeat(10000)
+ runBlocking {
+ cookieDao.save(cookie)
+ dao.save(cookie.id, type, content)
+ val cache = dao.select(cookie.id, type) ?: fail("Cache not found")
+ assertEquals(content, cache.contents, "Content mismatch")
+ assertTrue(
+ System.currentTimeMillis() - cache.lastUpdated < 500,
+ "Cache retrieval took over 500ms (${System.currentTimeMillis() - cache.lastUpdated})"
+ )
+ }
+ }
+}
diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt
new file mode 100644
index 00000000..327ead86
--- /dev/null
+++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2019 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.db
+
+import kotlinx.coroutines.runBlocking
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+
+class CookieDbTest : BaseDbTest() {
+
+ private val dao get() = db.cookieDao()
+
+ @Test
+ fun basicCookie() {
+ val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie")
+ runBlocking {
+ dao.save(cookie)
+ val cookies = dao.selectAll()
+ assertEquals(listOf(cookie), cookies, "Cookie mismatch")
+ }
+ }
+
+ @Test
+ fun deleteCookie() {
+ val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie")
+
+ runBlocking {
+ dao.save(cookie)
+ dao.deleteById(cookie.id + 1)
+ assertEquals(
+ listOf(cookie),
+ dao.selectAll(),
+ "Cookie list should be the same after inexistent deletion"
+ )
+ dao.deleteById(cookie.id)
+ assertEquals(emptyList(), dao.selectAll(), "Cookie list should be empty after deletion")
+ }
+ }
+
+ @Test
+ fun insertReplaceCookie() {
+ val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie")
+ runBlocking {
+ dao.save(cookie)
+ assertEquals(listOf(cookie), dao.selectAll(), "Cookie insertion failed")
+ dao.save(cookie.copy(name = "testName2"))
+ assertEquals(
+ listOf(cookie.copy(name = "testName2")),
+ dao.selectAll(),
+ "Cookie replacement failed"
+ )
+ dao.save(cookie.copy(id = 123L))
+ assertEquals(
+ setOf(cookie.copy(id = 123L), cookie.copy(name = "testName2")),
+ dao.selectAll().toSet(),
+ "New cookie insertion failed"
+ )
+ }
+ }
+
+ @Test
+ fun selectCookie() {
+ val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie")
+ runBlocking {
+ dao.save(cookie)
+ assertEquals(cookie, dao.selectById(cookie.id), "Cookie selection failed")
+ assertNull(dao.selectById(cookie.id + 1), "Inexistent cookie selection failed")
+ }
+ }
+}
diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/DatabaseTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/DatabaseTest.kt
new file mode 100644
index 00000000..c79d212e
--- /dev/null
+++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/DatabaseTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.db
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.runner.RunWith
+import org.koin.error.NoBeanDefFoundException
+import org.koin.standalone.get
+import org.koin.test.KoinTest
+import kotlin.reflect.KClass
+import kotlin.reflect.full.functions
+import kotlin.test.Test
+import kotlin.test.assertTrue
+
+@RunWith(AndroidJUnit4::class)
+class DatabaseTest : KoinTest {
+
+ inline fun <reified T : Any> hasKoin() = hasKoin(T::class)
+
+ fun <T : Any> hasKoin(klazz: KClass<T>): Boolean =
+ try {
+ get<T>(clazz = klazz)
+ true
+ } catch (e: NoBeanDefFoundException) {
+ false
+ }
+
+ /**
+ * Database and all daos should be loaded as components
+ */
+ @Test
+ fun testKoins() {
+ hasKoin<FrostDatabase>()
+ val members = FrostDatabase::class.java.kotlin.functions.filter { it.name.endsWith("Dao") }
+ .mapNotNull { it.returnType.classifier as? KClass<*> }
+ assertTrue(members.isNotEmpty(), "Failed to find dao interfaces")
+ val missingKoins = (members + FrostDatabase::class).filter { !hasKoin(it) }
+ assertTrue(missingKoins.isEmpty(), "Missing koins: $missingKoins")
+ }
+}
diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/GenericDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/GenericDbTest.kt
new file mode 100644
index 00000000..add9f509
--- /dev/null
+++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/GenericDbTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2019 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.db
+
+import com.pitchedapps.frost.facebook.FbItem
+import com.pitchedapps.frost.facebook.defaultTabs
+import kotlinx.coroutines.runBlocking
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class GenericDbTest : BaseDbTest() {
+
+ private val dao get() = db.genericDao()
+
+ /**
+ * Note that order is also preserved here
+ */
+ @Test
+ fun save() {
+ val tabs = listOf(FbItem.ACTIVITY_LOG, FbItem.BIRTHDAYS, FbItem.EVENTS, FbItem.MARKETPLACE, FbItem.ACTIVITY_LOG)
+ runBlocking {
+ dao.saveTabs(tabs)
+ assertEquals(tabs, dao.getTabs(), "Tab saving failed")
+ val newTabs = listOf(FbItem.PAGES, FbItem.MENU)
+ dao.saveTabs(newTabs)
+ assertEquals(newTabs, dao.getTabs(), "Tab overwrite failed")
+ }
+ }
+
+ @Test
+ fun defaultRetrieve() {
+ runBlocking {
+ assertEquals(defaultTabs(), dao.getTabs(), "Default retrieval failed")
+ }
+ }
+
+ @Test
+ fun ignoreErrors() {
+ runBlocking {
+ dao.save(GenericEntity(GenericDao.TYPE_TABS, "${FbItem.ACTIVITY_LOG.name},unknown,${FbItem.EVENTS.name}"))
+ assertEquals(
+ listOf(FbItem.ACTIVITY_LOG, FbItem.EVENTS),
+ dao.getTabs(),
+ "Tab fetching does not ignore unknown names"
+ )
+ }
+ }
+}
diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt
new file mode 100644
index 00000000..45a09cbe
--- /dev/null
+++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2019 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.db
+
+import com.pitchedapps.frost.services.NOTIF_CHANNEL_GENERAL
+import com.pitchedapps.frost.services.NOTIF_CHANNEL_MESSAGES
+import com.pitchedapps.frost.services.NotificationContent
+import kotlinx.coroutines.runBlocking
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class NotificationDbTest : BaseDbTest() {
+
+ private val dao get() = db.notifDao()
+
+ private fun cookie(id: Long) = CookieEntity(id, "name$id", "cookie$id")
+
+ private fun notifContent(id: Long, cookie: CookieEntity, time: Long = id) = NotificationContent(
+ data = cookie,
+ id = id,
+ href = "",
+ title = null,
+ text = "",
+ timestamp = time,
+ profileUrl = null,
+ unread = true
+ )
+
+ @Test
+ fun saveAndRetrieve() {
+ val cookie = cookie(12345L)
+ // Unique unsorted ids
+ val notifs = listOf(0L, 4L, 2L, 6L, 99L, 3L).map { notifContent(it, cookie) }
+ runBlocking {
+ db.cookieDao().save(cookie)
+ dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs)
+ val dbNotifs = dao.selectNotifications(cookie.id, NOTIF_CHANNEL_GENERAL)
+ assertEquals(notifs.sortedByDescending { it.timestamp }, dbNotifs, "Incorrect notification list received")
+ }
+ }
+
+ @Test
+ fun selectConditions() {
+ runBlocking {
+ val cookie1 = cookie(12345L)
+ val cookie2 = cookie(12L)
+ val notifs1 = (0L..2L).map { notifContent(it, cookie1) }
+ val notifs2 = (5L..10L).map { notifContent(it, cookie2) }
+ db.cookieDao().save(cookie1)
+ db.cookieDao().save(cookie2)
+ dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs1)
+ dao.saveNotifications(NOTIF_CHANNEL_MESSAGES, notifs2)
+ assertEquals(
+ emptyList(),
+ dao.selectNotifications(cookie1.id, NOTIF_CHANNEL_MESSAGES),
+ "Filtering by type did not work for cookie1"
+ )
+ assertEquals(
+ notifs1.sortedByDescending { it.timestamp },
+ dao.selectNotifications(cookie1.id, NOTIF_CHANNEL_GENERAL),
+ "Selection for cookie1 failed"
+ )
+ assertEquals(
+ emptyList(),
+ dao.selectNotifications(cookie2.id, NOTIF_CHANNEL_GENERAL),
+ "Filtering by type did not work for cookie2"
+ )
+ assertEquals(
+ notifs2.sortedByDescending { it.timestamp },
+ dao.selectNotifications(cookie2.id, NOTIF_CHANNEL_MESSAGES),
+ "Selection for cookie2 failed"
+ )
+ }
+ }
+
+ /**
+ * Primary key is both id and userId, in the event that the same notification to multiple users has the same id
+ */
+ @Test
+ fun primaryKeyCheck() {
+ runBlocking {
+ val cookie1 = cookie(12345L)
+ val cookie2 = cookie(12L)
+ val notifs1 = (0L..2L).map { notifContent(it, cookie1) }
+ val notifs2 = notifs1.map { it.copy(data = cookie2) }
+ db.cookieDao().save(cookie1)
+ db.cookieDao().save(cookie2)
+ assertTrue(dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs1), "Notif1 save failed")
+ assertTrue(dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs2), "Notif2 save failed")
+ }
+ }
+
+ @Test
+ fun cascadeDeletion() {
+ val cookie = cookie(12345L)
+ // Unique unsorted ids
+ val notifs = listOf(0L, 4L, 2L, 6L, 99L, 3L).map { notifContent(it, cookie) }
+ runBlocking {
+ db.cookieDao().save(cookie)
+ dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs)
+ db.cookieDao().deleteById(cookie.id)
+ val dbNotifs = dao.selectNotifications(cookie.id, NOTIF_CHANNEL_GENERAL)
+ assertTrue(dbNotifs.isEmpty(), "Cascade deletion failed")
+ }
+ }
+
+ @Test
+ fun latestEpoch() {
+ val cookie = cookie(12345L)
+ // Unique unsorted ids
+ val notifs = listOf(0L, 4L, 2L, 6L, 99L, 3L).map { notifContent(it, cookie) }
+ runBlocking {
+ assertEquals(-1L, dao.latestEpoch(cookie.id, NOTIF_CHANNEL_GENERAL), "Default epoch failed")
+ db.cookieDao().save(cookie)
+ dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs)
+ assertEquals(99L, dao.latestEpoch(cookie.id, NOTIF_CHANNEL_GENERAL), "Latest epoch failed")
+ }
+ }
+
+ @Test
+ fun insertionWithInvalidCookies() {
+ runBlocking {
+ assertFalse(
+ dao.saveNotifications(NOTIF_CHANNEL_GENERAL, listOf(notifContent(1L, cookie(2L)))),
+ "Notif save should not have passed without relevant cookie entries"
+ )
+ }
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 55d2ca02..bd8776d1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -174,6 +174,20 @@
</intent-filter>
</receiver>
+ <!--Widgets-->
+ <receiver android:name=".widgets.NotificationWidget">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/notification_widget_info" />
+ </receiver>
+ <service
+ android:name=".widgets.NotificationWidgetService"
+ android:enabled="true"
+ android:permission="android.permission.BIND_REMOTEVIEWS" />
+
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
index 5b62afad..41c6ff4b 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
@@ -29,9 +29,10 @@ import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.signature.ApplicationVersionSignature
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader
-import com.pitchedapps.frost.dbflow.CookiesDb
-import com.pitchedapps.frost.dbflow.FbTabsDb
-import com.pitchedapps.frost.dbflow.NotificationDb
+import com.pitchedapps.frost.db.CookiesDb
+import com.pitchedapps.frost.db.FbTabsDb
+import com.pitchedapps.frost.db.FrostDatabase
+import com.pitchedapps.frost.db.NotificationDb
import com.pitchedapps.frost.glide.GlideApp
import com.pitchedapps.frost.services.scheduleNotifications
import com.pitchedapps.frost.services.setupNotificationChannels
@@ -44,6 +45,7 @@ import com.raizlabs.android.dbflow.config.DatabaseConfig
import com.raizlabs.android.dbflow.config.FlowConfig
import com.raizlabs.android.dbflow.config.FlowManager
import com.raizlabs.android.dbflow.runtime.ContentResolverNotifier
+import org.koin.android.ext.android.startKoin
import java.util.Random
import kotlin.reflect.KClass
@@ -81,7 +83,7 @@ class FrostApp : Application() {
)
Showcase.initialize(this, "${BuildConfig.APPLICATION_ID}.showcase")
Prefs.initialize(this, "${BuildConfig.APPLICATION_ID}.prefs")
- // if (LeakCanary.isInAnalyzerProcess(this)) return
+// if (LeakCanary.isInAnalyzerProcess(this)) return
// refWatcher = LeakCanary.install(this)
initBugsnag()
KL.shouldLog = { BuildConfig.DEBUG }
@@ -132,6 +134,7 @@ class FrostApp : Application() {
L.d { "Activity ${activity.localClassName} created" }
}
})
+ startKoin(this, listOf(FrostDatabase.module(this)))
}
private fun initBugsnag() {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt
index 4c7aeedc..cf8acdd3 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt
@@ -32,8 +32,15 @@ import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.activities.LoginActivity
import com.pitchedapps.frost.activities.MainActivity
import com.pitchedapps.frost.activities.SelectorActivity
-import com.pitchedapps.frost.dbflow.CookieModel
-import com.pitchedapps.frost.dbflow.loadFbCookiesSync
+import com.pitchedapps.frost.db.CookieDao
+import com.pitchedapps.frost.db.CookieEntity
+import com.pitchedapps.frost.db.CookieModel
+import com.pitchedapps.frost.db.FbTabModel
+import com.pitchedapps.frost.db.GenericDao
+import com.pitchedapps.frost.db.getTabs
+import com.pitchedapps.frost.db.save
+import com.pitchedapps.frost.db.saveTabs
+import com.pitchedapps.frost.db.selectAll
import com.pitchedapps.frost.facebook.FbCookie
import com.pitchedapps.frost.utils.BiometricUtils
import com.pitchedapps.frost.utils.EXTRA_COOKIES
@@ -41,9 +48,12 @@ import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.launchNewTask
import com.pitchedapps.frost.utils.loadAssets
+import com.raizlabs.android.dbflow.kotlinextensions.from
+import com.raizlabs.android.dbflow.kotlinextensions.select
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import org.koin.android.ext.android.inject
import java.util.ArrayList
/**
@@ -51,6 +61,9 @@ import java.util.ArrayList
*/
class StartActivity : KauBaseActivity() {
+ private val cookieDao: CookieDao by inject()
+ private val genericDao: GenericDao by inject()
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -69,12 +82,11 @@ class StartActivity : KauBaseActivity() {
launch {
val authDefer = BiometricUtils.authenticate(this@StartActivity)
try {
+ migrate()
FbCookie.switchBackUser()
- val cookies = ArrayList(withContext(Dispatchers.IO) {
- loadFbCookiesSync()
- })
+ val cookies = ArrayList(cookieDao.selectAll())
L.i { "Cookies loaded at time ${System.currentTimeMillis()}" }
- L._d { "Cookies: ${cookies.joinToString("\t", transform = CookieModel::toSensitiveString)}" }
+ L._d { "Cookies: ${cookies.joinToString("\t", transform = CookieEntity::toSensitiveString)}" }
loadAssets()
authDefer.await()
when {
@@ -88,11 +100,32 @@ class StartActivity : KauBaseActivity() {
})
}
} catch (e: Exception) {
+ L._e(e) { "Load start failed" }
showInvalidWebView()
}
}
}
+ /**
+ * Migrate from dbflow to room
+ * TODO delete dbflow data
+ */
+ private suspend fun migrate() = withContext(Dispatchers.IO) {
+ if (cookieDao.selectAll().isNotEmpty()) return@withContext
+ val cookies = (select from CookieModel::class).queryList().map { CookieEntity(it.id, it.name, it.cookie) }
+ if (cookies.isNotEmpty()) {
+ cookieDao.save(cookies)
+ L._d { "Migrated cookies ${cookieDao.selectAll()}" }
+ }
+ val tabs = (select from FbTabModel::class).queryList().map(FbTabModel::tab)
+ if (tabs.isNotEmpty()) {
+ genericDao.saveTabs(tabs)
+ L._d { "Migrated tabs ${genericDao.getTabs()}" }
+ }
+ deleteDatabase("Cookies.db")
+ deleteDatabase("FrostTabs.db")
+ }
+
private fun showInvalidWebView() =
showInvalidView(R.string.error_webview)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt
index f1d88bc3..49d5f8bf 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt
@@ -69,9 +69,10 @@ import com.pitchedapps.frost.contracts.FileChooserContract
import com.pitchedapps.frost.contracts.FileChooserDelegate
import com.pitchedapps.frost.contracts.MainActivityContract
import com.pitchedapps.frost.contracts.VideoViewHolder
-import com.pitchedapps.frost.dbflow.TAB_COUNT
-import com.pitchedapps.frost.dbflow.loadFbCookie
-import com.pitchedapps.frost.dbflow.loadFbTabs
+import com.pitchedapps.frost.db.CookieDao
+import com.pitchedapps.frost.db.GenericDao
+import com.pitchedapps.frost.db.currentCookie
+import com.pitchedapps.frost.db.getTabs
import com.pitchedapps.frost.enums.MainActivityLayout
import com.pitchedapps.frost.facebook.FbCookie
import com.pitchedapps.frost.facebook.FbItem
@@ -103,12 +104,14 @@ import com.pitchedapps.frost.utils.setFrostColors
import com.pitchedapps.frost.views.BadgedIcon
import com.pitchedapps.frost.views.FrostVideoViewer
import com.pitchedapps.frost.views.FrostViewPager
+import com.pitchedapps.frost.widgets.NotificationWidget
import kotlinx.android.synthetic.main.activity_frame_wrapper.*
import kotlinx.android.synthetic.main.view_main_fab.*
import kotlinx.android.synthetic.main.view_main_toolbar.*
import kotlinx.android.synthetic.main.view_main_viewpager.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
+import org.koin.android.ext.android.inject
/**
* Created by Allan Wang on 20/12/17.
@@ -120,9 +123,14 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
FileChooserContract by FileChooserDelegate(),
VideoViewHolder, SearchViewHolder {
- protected lateinit var adapter: SectionsPagerAdapter
+ /**
+ * Note that tabs themselves are initialized through a coroutine during onCreate
+ */
+ protected val adapter: SectionsPagerAdapter = SectionsPagerAdapter()
override val frameWrapper: FrameLayout get() = frame_wrapper
val viewPager: FrostViewPager get() = container
+ val cookieDao: CookieDao by inject()
+ val genericDao: GenericDao by inject()
/*
* Components with the same id in multiple layout files
@@ -131,6 +139,8 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
val appBar: AppBarLayout by bindView(R.id.appbar)
val coordinator: CoordinatorLayout by bindView(R.id.main_content)
+ protected var lastPosition = -1
+
override var videoViewer: FrostVideoViewer? = null
private lateinit var drawer: Drawer
private lateinit var drawerHeader: AccountHeader
@@ -151,12 +161,13 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
background(viewPager)
}
setSupportActionBar(toolbar)
- adapter = SectionsPagerAdapter(loadFbTabs())
viewPager.adapter = adapter
- viewPager.offscreenPageLimit = TAB_COUNT
tabs.setBackgroundColor(Prefs.mainActivityLayout.backgroundColor())
onNestedCreate(savedInstanceState)
L.i { "Main finished loading UI in ${System.currentTimeMillis() - start} ms" }
+ launch {
+ adapter.setPages(genericDao.getTabs())
+ }
controlWebview = WebView(this)
if (BuildConfig.VERSION_CODE > Prefs.versionCode) {
Prefs.prevVersionCode = Prefs.versionCode
@@ -274,27 +285,28 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
if (current) launchWebOverlay(FbItem.PROFILE.url)
else when (profile.identifier) {
-2L -> {
- val currentCookie = loadFbCookie(Prefs.userId)
- if (currentCookie == null) {
- toast(R.string.account_not_found)
- launch {
+ // TODO no backpressure support
+ this@BaseMainActivity.launch {
+ val currentCookie = cookieDao.currentCookie()
+ if (currentCookie == null) {
+ toast(R.string.account_not_found)
FbCookie.reset()
launchLogin(cookies(), true)
- }
- } else {
- materialDialogThemed {
- title(R.string.kau_logout)
- content(
- String.format(
- string(R.string.kau_logout_confirm_as_x), currentCookie.name
- ?: Prefs.userId.toString()
+ } else {
+ materialDialogThemed {
+ title(R.string.kau_logout)
+ content(
+ String.format(
+ string(R.string.kau_logout_confirm_as_x),
+ currentCookie.name ?: Prefs.userId.toString()
+ )
)
- )
- positiveText(R.string.kau_yes)
- negativeText(R.string.kau_no)
- onPositive { _, _ ->
- launch {
- FbCookie.logout(this@BaseMainActivity)
+ positiveText(R.string.kau_yes)
+ negativeText(R.string.kau_no)
+ onPositive { _, _ ->
+ this@BaseMainActivity.launch {
+ FbCookie.logout(this@BaseMainActivity)
+ }
}
}
}
@@ -303,7 +315,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
-3L -> launchNewTask<LoginActivity>(clearStack = false)
-4L -> launchNewTask<SelectorActivity>(cookies(), false)
else -> {
- launch {
+ this@BaseMainActivity.launch {
FbCookie.switchUser(profile.identifier)
tabsForEachView { _, view -> view.badgeText = null }
refreshAll()
@@ -371,6 +383,11 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
R.id.action_settings to GoogleMaterial.Icon.gmd_settings,
R.id.action_search to GoogleMaterial.Icon.gmd_search
)
+ bindSearchView(menu)
+ return true
+ }
+
+ private fun bindSearchView(menu: Menu) {
searchViewBindIfNull {
bindSearchView(menu, R.id.action_search, Prefs.iconColor) {
textCallback = { query, searchView ->
@@ -402,7 +419,6 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
onItemClick = { _, key, _, _ -> launchWebOverlay(key) }
}
}
- return true
}
@SuppressLint("RestrictedApi")
@@ -439,7 +455,11 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
Runtime.getRuntime().exit(0)
return
}
- if (resultCode and REQUEST_RESTART > 0) return restart()
+ if (resultCode and REQUEST_RESTART > 0) {
+ NotificationWidget.forceUpdate(this)
+ restart()
+ return
+ }
/*
* These results can be stacked
*/
@@ -454,16 +474,12 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
- outState.putStringArrayList(STATE_FORCE_FALLBACK, ArrayList(adapter.forcedFallbacks))
+ adapter.saveInstanceState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
- adapter.forcedFallbacks.clear()
- adapter.forcedFallbacks.addAll(
- savedInstanceState.getStringArrayList(STATE_FORCE_FALLBACK)
- ?: emptyList()
- )
+ adapter.restoreInstanceState(savedInstanceState)
}
override fun onResume() {
@@ -496,6 +512,10 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
}
override fun backConsumer(): Boolean {
+ if (drawer.isDrawerOpen) {
+ drawer.closeDrawer()
+ return true
+ }
if (currentFragment.onBackPressed()) return true
if (Prefs.exitConfirmation) {
materialDialogThemed {
@@ -518,9 +538,48 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
runOnUiThread { adapter.reloadFragment(fragment) }
}
- inner class SectionsPagerAdapter(val pages: List<FbItem>) : FragmentPagerAdapter(supportFragmentManager) {
+ inner class SectionsPagerAdapter : FragmentPagerAdapter(supportFragmentManager) {
+
+ private val pages: MutableList<FbItem> = mutableListOf()
+
+ private val forcedFallbacks = mutableSetOf<String>()
+
+ /**
+ * Update page list and prompt reload
+ */
+ fun setPages(pages: List<FbItem>) {
+ this.pages.clear()
+ this.pages.addAll(pages)
+ notifyDataSetChanged()
+ tabs.removeAllTabs()
+ this.pages.forEachIndexed { index, fbItem ->
+ tabs.addTab(
+ tabs.newTab()
+ .setCustomView(BadgedIcon(this@BaseMainActivity).apply { iicon = fbItem.icon }.also {
+ it.setAllAlpha(if (index == 0) SELECTED_TAB_ALPHA else UNSELECTED_TAB_ALPHA)
+ })
+ )
+ }
+ lastPosition = 0
+ viewPager.setCurrentItem(0, false)
+ viewPager.offscreenPageLimit = pages.size
+ viewPager.post {
+ if (!fragmentChannel.isClosedForSend)
+ fragmentChannel.offer(0)
+ } //trigger hook so title is set
+ }
+
+ fun saveInstanceState(outState: Bundle) {
+ outState.putStringArrayList(STATE_FORCE_FALLBACK, ArrayList(forcedFallbacks))
+ }
- val forcedFallbacks = mutableSetOf<String>()
+ fun restoreInstanceState(savedInstanceState: Bundle) {
+ forcedFallbacks.clear()
+ forcedFallbacks.addAll(
+ savedInstanceState.getStringArrayList(STATE_FORCE_FALLBACK)
+ ?: emptyList()
+ )
+ }
fun reloadFragment(fragment: BaseFragment) {
if (fragment is WebFragment) return
@@ -559,4 +618,9 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
PointF(0f, toolbar.height.toFloat())
else
PointF(0f, 0f)
+
+ companion object {
+ const val SELECTED_TAB_ALPHA = 255f
+ const val UNSELECTED_TAB_ALPHA = 128f
+ }
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt
index 04f67276..1e106765 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt
@@ -54,6 +54,7 @@ import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.cookies
import com.pitchedapps.frost.utils.launchNewTask
import com.pitchedapps.frost.utils.loadAssets
+import com.pitchedapps.frost.widgets.NotificationWidget
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
@@ -171,6 +172,7 @@ class IntroActivity : KauBaseActivity(), ViewPager.PageTransformer, ViewPager.On
override fun finish() {
launch(NonCancellable) {
loadAssets()
+ NotificationWidget.forceUpdate(this@IntroActivity)
launchNewTask<MainActivity>(cookies(), false)
super.finish()
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
index 97abf5a2..ed207896 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
@@ -32,9 +32,10 @@ import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.pitchedapps.frost.R
-import com.pitchedapps.frost.dbflow.CookieModel
-import com.pitchedapps.frost.dbflow.loadFbCookiesSuspend
-import com.pitchedapps.frost.dbflow.saveFbCookie
+import com.pitchedapps.frost.db.CookieDao
+import com.pitchedapps.frost.db.CookieEntity
+import com.pitchedapps.frost.db.save
+import com.pitchedapps.frost.db.selectAll
import com.pitchedapps.frost.facebook.FbCookie
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.profilePictureUrl
@@ -58,6 +59,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
+import org.koin.android.ext.android.inject
import java.net.UnknownHostException
import kotlin.coroutines.resume
@@ -71,6 +73,7 @@ class LoginActivity : BaseActivity() {
private val swipeRefresh: SwipeRefreshLayout by bindView(R.id.swipe_refresh)
private val textview: AppCompatTextView by bindView(R.id.textview)
private val profile: ImageView by bindView(R.id.profile)
+ private val cookieDao: CookieDao by inject()
private lateinit var profileLoader: RequestManager
private val refreshChannel = Channel<Boolean>(10)
@@ -109,13 +112,13 @@ class LoginActivity : BaseActivity() {
refreshChannel.offer(refreshing)
}
- private suspend fun loadInfo(cookie: CookieModel): Unit = withMainContext {
+ private suspend fun loadInfo(cookie: CookieEntity): Unit = withMainContext {
refresh(true)
val imageDeferred = async { loadProfile(cookie.id) }
val nameDeferred = async { loadUsername(cookie) }
- val name: String = nameDeferred.await()
+ val name: String? = nameDeferred.await()
val foundImage: Boolean = imageDeferred.await()
L._d { "Logged in and received data" }
@@ -126,7 +129,7 @@ class LoginActivity : BaseActivity() {
L._i { cookie }
}
- textview.text = String.format(getString(R.string.welcome), name)
+ textview.text = String.format(getString(R.string.welcome), name ?: "")
textview.fadeIn()
frostEvent("Login", "success" to true)
@@ -134,7 +137,7 @@ class LoginActivity : BaseActivity() {
* The user may have logged into an account that is already in the database
* We will let the db handle duplicates and load it now after the new account has been saved
*/
- val cookies = ArrayList(loadFbCookiesSuspend())
+ val cookies = ArrayList(cookieDao.selectAll())
delay(1000)
if (Showcase.intro)
launchNewTask<IntroActivity>(cookies, true)
@@ -171,23 +174,23 @@ class LoginActivity : BaseActivity() {
}
}
- private suspend fun loadUsername(cookie: CookieModel): String = withContext(Dispatchers.IO) {
- val result: String = try {
+ private suspend fun loadUsername(cookie: CookieEntity): String? = withContext(Dispatchers.IO) {
+ val result: String? = try {
withTimeout(5000) {
frostJsoup(cookie.cookie, FbItem.PROFILE.url).title()
}
} catch (e: Exception) {
if (e !is UnknownHostException)
e.logFrostEvent("Fetch username failed")
- ""
+ null
}
- if (cookie.name?.isNotBlank() == false && result != cookie.name) {
- cookie.name = result
- saveFbCookie(cookie)
+ if (result != null) {
+ cookieDao.save(cookie.copy(name = result))
+ return@withContext result
}
- cookie.name ?: ""
+ return@withContext cookie.name
}
override fun backConsumer(): Boolean {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
index 75c9537b..34674cb0 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
@@ -35,7 +35,6 @@ class MainActivity : BaseMainActivity() {
override val fragmentChannel = BroadcastChannel<Int>(10)
override val headerBadgeChannel = BroadcastChannel<String>(Channel.CONFLATED)
- var lastPosition = -1
override fun onNestedCreate(savedInstanceState: Bundle?) {
setupTabs()
@@ -54,23 +53,18 @@ class MainActivity : BaseMainActivity() {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
- val delta = positionOffset * (255 - 128).toFloat()
+ val delta = positionOffset * (SELECTED_TAB_ALPHA - UNSELECTED_TAB_ALPHA)
tabsForEachView { tabPosition, view ->
view.setAllAlpha(
when (tabPosition) {
- position -> 255.0f - delta
- position + 1 -> 128.0f + delta
- else -> 128f
+ position -> SELECTED_TAB_ALPHA - delta
+ position + 1 -> UNSELECTED_TAB_ALPHA + delta
+ else -> UNSELECTED_TAB_ALPHA
}
)
}
}
})
- viewPager.post {
- if (!fragmentChannel.isClosedForSend)
- fragmentChannel.offer(0)
- lastPosition = 0
- } //trigger hook so title is set
}
private fun setupTabs() {
@@ -115,11 +109,5 @@ class MainActivity : BaseMainActivity() {
L.e(e) { "Header badge error" }
}
}
- adapter.pages.forEach {
- tabs.addTab(
- tabs.newTab()
- .setCustomView(BadgedIcon(this).apply { iicon = it.icon })
- )
- }
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt
index 7f632940..6ad7d3f2 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt
@@ -25,6 +25,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import ca.allanwang.kau.kotlin.lazyContext
+import ca.allanwang.kau.utils.launchMain
import ca.allanwang.kau.utils.scaleXY
import ca.allanwang.kau.utils.setIcon
import ca.allanwang.kau.utils.withAlpha
@@ -33,14 +34,19 @@ import com.mikepenz.fastadapter_extensions.drag.ItemTouchCallback
import com.mikepenz.fastadapter_extensions.drag.SimpleDragCallback
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.R
-import com.pitchedapps.frost.dbflow.TAB_COUNT
-import com.pitchedapps.frost.dbflow.loadFbTabs
-import com.pitchedapps.frost.dbflow.save
+import com.pitchedapps.frost.db.GenericDao
+import com.pitchedapps.frost.db.TAB_COUNT
+import com.pitchedapps.frost.db.getTabs
+import com.pitchedapps.frost.db.saveTabs
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.iitems.TabIItem
+import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.setFrostColors
import kotlinx.android.synthetic.main.activity_tab_customizer.*
+import kotlinx.coroutines.NonCancellable
+import kotlinx.coroutines.launch
+import org.koin.android.ext.android.inject
import java.util.Collections
/**
@@ -48,6 +54,8 @@ import java.util.Collections
*/
class TabCustomizerActivity : BaseActivity() {
+ private val genericDao: GenericDao by inject()
+
private val adapter = FastItemAdapter<TabIItem>()
private val wobble = lazyContext { AnimationUtils.loadAnimation(it, R.anim.rotate_delta) }
@@ -65,24 +73,30 @@ class TabCustomizerActivity : BaseActivity() {
divider.setBackgroundColor(Prefs.textColor.withAlpha(30))
instructions.setTextColor(Prefs.textColor)
- val tabs = loadFbTabs().toMutableList()
- val remaining = FbItem.values().filter { it.name[0] != '_' }.toMutableList()
- remaining.removeAll(tabs)
- tabs.addAll(remaining)
+ launch {
+ val tabs = genericDao.getTabs().toMutableList()
+ L.d { "Tabs $tabs" }
+ val remaining = FbItem.values().filter { it.name[0] != '_' }.toMutableList()
+ remaining.removeAll(tabs)
+ tabs.addAll(remaining)
+ adapter.set(tabs.map(::TabIItem))
- adapter.add(tabs.map(::TabIItem))
- bindSwapper(adapter, tab_recycler)
+ bindSwapper(adapter, tab_recycler)
- adapter.withOnClickListener { view, _, _, _ -> view!!.wobble(); true }
+ adapter.withOnClickListener { view, _, _, _ -> view!!.wobble(); true }
+ }
setResult(Activity.RESULT_CANCELED)
fab_save.setIcon(GoogleMaterial.Icon.gmd_check, Prefs.iconColor)
fab_save.backgroundTintList = ColorStateList.valueOf(Prefs.accentColor)
fab_save.setOnClickListener {
- adapter.adapterItems.subList(0, TAB_COUNT).map(TabIItem::item).save()
- setResult(Activity.RESULT_OK)
- finish()
+ launchMain(NonCancellable) {
+ val tabs = adapter.adapterItems.subList(0, TAB_COUNT).map(TabIItem::item)
+ genericDao.saveTabs(tabs)
+ setResult(Activity.RESULT_OK)
+ finish()
+ }
}
fab_cancel.setIcon(GoogleMaterial.Icon.gmd_close, Prefs.iconColor)
fab_cancel.backgroundTintList = ColorStateList.valueOf(Prefs.accentColor)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
index bb145b4f..ec4ff9dd 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
@@ -315,8 +315,8 @@ open class WebOverlayActivityBase(private val forceDesktopAgent: Boolean) : Base
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
- R.id.action_copy_link -> copyToClipboard(web.currentUrl)
- R.id.action_share -> shareText(web.currentUrl)
+ R.id.action_copy_link -> copyToClipboard(web.currentUrl.formattedFbUrl)
+ R.id.action_share -> shareText(web.currentUrl.formattedFbUrl)
else -> if (!OverlayContext.onOptionsItemSelected(web, item.itemId))
return super.onOptionsItemSelected(item)
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt
new file mode 100644
index 00000000..f0dacdc7
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2018 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.db
+
+import android.os.Parcelable
+import androidx.room.Dao
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.pitchedapps.frost.utils.L
+import kotlinx.android.parcel.Parcelize
+
+/**
+ * Created by Allan Wang on 2017-05-30.
+ */
+
+/**
+ * Generic cache to store serialized content
+ */
+@Entity(
+ tableName = "frost_cache",
+ primaryKeys = ["id", "type"],
+ foreignKeys = [ForeignKey(
+ entity = CookieEntity::class,
+ parentColumns = ["cookie_id"],
+ childColumns = ["id"],
+ onDelete = ForeignKey.CASCADE
+ )]
+)
+@Parcelize
+data class CacheEntity(
+ val id: Long,
+ val type: String,
+ val lastUpdated: Long,
+ val contents: String
+) : Parcelable
+
+@Dao
+interface CacheDao {
+
+ @Query("SELECT * FROM frost_cache WHERE id = :id AND type = :type")
+ fun _select(id: Long, type: String): CacheEntity?
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun _insertCache(cache: CacheEntity)
+
+ @Query("DELETE FROM frost_cache WHERE id = :id AND type = :type")
+ fun _delete(id: Long, type: String)
+}
+
+suspend fun CacheDao.select(id: Long, type: String) = dao {
+ _select(id, type)
+}
+
+suspend fun CacheDao.delete(id: Long, type: String) = dao {
+ _delete(id, type)
+}
+
+/**
+ * Returns true if successful, given that there are constraints to the insertion
+ */
+suspend fun CacheDao.save(id: Long, type: String, contents: String): Boolean = dao {
+ try {
+ _insertCache(CacheEntity(id, type, System.currentTimeMillis(), contents))
+ true
+ } catch (e: Exception) {
+ L.e(e) { "Cache save failed for $type" }
+ false
+ }
+}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt
new file mode 100644
index 00000000..b81ce365
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2018 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.db
+
+import android.os.Parcelable
+import androidx.room.ColumnInfo
+import androidx.room.Dao
+import androidx.room.Entity
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.pitchedapps.frost.utils.Prefs
+import com.raizlabs.android.dbflow.annotation.ConflictAction
+import com.raizlabs.android.dbflow.annotation.Database
+import com.raizlabs.android.dbflow.annotation.PrimaryKey
+import com.raizlabs.android.dbflow.annotation.Table
+import com.raizlabs.android.dbflow.structure.BaseModel
+import kotlinx.android.parcel.Parcelize
+
+/**
+ * Created by Allan Wang on 2017-05-30.
+ */
+
+@Entity(tableName = "cookies")
+@Parcelize
+data class CookieEntity(
+ @androidx.room.PrimaryKey
+ @ColumnInfo(name = "cookie_id")
+ val id: Long,
+ val name: String?,
+ val cookie: String?
+) : Parcelable {
+ override fun toString(): String = "CookieEntity(${hashCode()})"
+
+ fun toSensitiveString(): String = "CookieEntity(id=$id, name=$name, cookie=$cookie)"
+}
+
+@Dao
+interface CookieDao {
+
+ @Query("SELECT * FROM cookies")
+ fun _selectAll(): List<CookieEntity>
+
+ @Query("SELECT * FROM cookies WHERE cookie_id = :id")
+ fun _selectById(id: Long): CookieEntity?
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun _save(cookie: CookieEntity)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun _save(cookies: List<CookieEntity>)
+
+ @Query("DELETE FROM cookies WHERE cookie_id = :id")
+ fun _deleteById(id: Long)
+}
+
+suspend fun CookieDao.selectAll() = dao { _selectAll() }
+suspend fun CookieDao.selectById(id: Long) = dao { _selectById(id) }
+suspend fun CookieDao.save(cookie: CookieEntity) = dao { _save(cookie) }
+suspend fun CookieDao.save(cookies: List<CookieEntity>) = dao { _save(cookies) }
+suspend fun CookieDao.deleteById(id: Long) = dao { _deleteById(id) }
+suspend fun CookieDao.currentCookie() = selectById(Prefs.userId)
+
+@Database(version = CookiesDb.VERSION)
+object CookiesDb {
+ const val NAME = "Cookies"
+ const val VERSION = 2
+}
+
+@Parcelize
+@Table(database = CookiesDb::class, allFields = true, primaryKeyConflict = ConflictAction.REPLACE)
+data class CookieModel(@PrimaryKey var id: Long = -1L, var name: String? = null, var cookie: String? = null) :
+ BaseModel(), Parcelable {
+
+ override fun toString(): String = "CookieModel(${hashCode()})"
+}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/DaoUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/DaoUtils.kt
new file mode 100644
index 00000000..c31aa9b7
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/db/DaoUtils.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.db
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+/**
+ * Wraps dao calls to work with coroutines
+ * Non transactional queries were supposed to be fixed in https://issuetracker.google.com/issues/69474692,
+ * but it still requires dispatch from a non ui thread.
+ * This avoids that constraint
+ */
+suspend inline fun <T> dao(crossinline block: () -> T) = withContext(Dispatchers.IO) { block() }
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt
new file mode 100644
index 00000000..67372e23
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2019 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.db
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import com.pitchedapps.frost.BuildConfig
+import org.koin.dsl.module.module
+import org.koin.standalone.StandAloneContext
+
+interface FrostPrivateDao {
+ fun cookieDao(): CookieDao
+ fun notifDao(): NotificationDao
+ fun cacheDao(): CacheDao
+}
+
+@Database(
+ entities = [CookieEntity::class, NotificationEntity::class, CacheEntity::class],
+ version = 1,
+ exportSchema = true
+)
+abstract class FrostPrivateDatabase : RoomDatabase(), FrostPrivateDao {
+ companion object {
+ const val DATABASE_NAME = "frost-priv-db"
+ }
+}
+
+interface FrostPublicDao {
+ fun genericDao(): GenericDao
+}
+
+@Database(entities = [GenericEntity::class], version = 1, exportSchema = true)
+abstract class FrostPublicDatabase : RoomDatabase(), FrostPublicDao {
+ companion object {
+ const val DATABASE_NAME = "frost-db"
+ }
+}
+
+interface FrostDao : FrostPrivateDao, FrostPublicDao {
+ fun close()
+}
+
+/**
+ * Composition of all database interfaces
+ */
+class FrostDatabase(private val privateDb: FrostPrivateDatabase, private val publicDb: FrostPublicDatabase) :
+ FrostDao,
+ FrostPrivateDao by privateDb,
+ FrostPublicDao by publicDb {
+
+ override fun close() {
+ privateDb.close()
+ publicDb.close()
+ }
+
+ companion object {
+
+ private fun <T : RoomDatabase> RoomDatabase.Builder<T>.frostBuild() = if (BuildConfig.DEBUG) {
+ fallbackToDestructiveMigration().build()
+ } else {
+ build()
+ }
+
+ fun create(context: Context): FrostDatabase {
+ val privateDb = Room.databaseBuilder(
+ context, FrostPrivateDatabase::class.java,
+ FrostPrivateDatabase.DATABASE_NAME
+ ).frostBuild()
+ val publicDb = Room.databaseBuilder(
+ context, FrostPublicDatabase::class.java,
+ FrostPublicDatabase.DATABASE_NAME
+ ).frostBuild()
+ return FrostDatabase(privateDb, publicDb)
+ }
+
+ fun module(context: Context) = module {
+ single { create(context) }
+ single { get<FrostDatabase>().cookieDao() }
+ single { get<FrostDatabase>().cacheDao() }
+ single { get<FrostDatabase>().notifDao() }
+ single { get<FrostDatabase>().genericDao() }
+ }
+
+ /**
+ * Get from koin
+ * For the most part, you can retrieve directly from other koin components
+ */
+ fun get(): FrostDatabase = StandAloneContext.getKoin().koinContext.get()
+ }
+}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt
index 9d330169..a704ce82 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt
@@ -14,23 +14,18 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-package com.pitchedapps.frost.dbflow
+package com.pitchedapps.frost.db
import com.pitchedapps.frost.facebook.FbItem
-import com.pitchedapps.frost.facebook.defaultTabs
-import com.pitchedapps.frost.utils.L
import com.raizlabs.android.dbflow.annotation.Database
import com.raizlabs.android.dbflow.annotation.PrimaryKey
import com.raizlabs.android.dbflow.annotation.Table
-import com.raizlabs.android.dbflow.kotlinextensions.database
-import com.raizlabs.android.dbflow.kotlinextensions.fastSave
-import com.raizlabs.android.dbflow.kotlinextensions.from
-import com.raizlabs.android.dbflow.kotlinextensions.select
import com.raizlabs.android.dbflow.structure.BaseModel
/**
* Created by Allan Wang on 2017-05-30.
*/
+
const val TAB_COUNT = 4
@Database(version = FbTabsDb.VERSION)
@@ -41,18 +36,3 @@ object FbTabsDb {
@Table(database = FbTabsDb::class, allFields = true)
data class FbTabModel(@PrimaryKey var position: Int = -1, var tab: FbItem = FbItem.FEED) : BaseModel()
-
-/**
- * Load tabs synchronously
- * Note that tab length should never be a big number anyways
- */
-fun loadFbTabs(): List<FbItem> {
- val tabs: List<FbTabModel>? = (select from (FbTabModel::class)).orderBy(FbTabModel_Table.position, true).queryList()
- if (tabs?.size == TAB_COUNT) return tabs.map(FbTabModel::tab)
- L.d { "No tabs (${tabs?.size}); loading default" }
- return defaultTabs()
-}
-
-fun List<FbItem>.save() {
- database<FbTabsDb>().beginTransactionAsync(mapIndexed(::FbTabModel).fastSave().build()).execute()
-}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt
new file mode 100644
index 00000000..f36c8af9
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2018 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.db
+
+import androidx.room.Dao
+import androidx.room.Entity
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.PrimaryKey
+import androidx.room.Query
+import com.pitchedapps.frost.facebook.FbItem
+import com.pitchedapps.frost.facebook.defaultTabs
+
+/**
+ * Created by Allan Wang on 2017-05-30.
+ */
+
+/**
+ * Generic cache to store serialized content
+ */
+@Entity(tableName = "frost_generic")
+data class GenericEntity(
+ @PrimaryKey
+ val type: String,
+ val contents: String
+)
+
+@Dao
+interface GenericDao {
+
+ @Query("SELECT contents FROM frost_generic WHERE type = :type")
+ fun _select(type: String): String?
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun _save(entity: GenericEntity)
+
+ @Query("DELETE FROM frost_generic WHERE type = :type")
+ fun _delete(type: String)
+
+ companion object {
+ const val TYPE_TABS = "generic_tabs"
+ }
+}
+
+suspend fun GenericDao.saveTabs(tabs: List<FbItem>) = dao {
+ val content = tabs.joinToString(",") { it.name }
+ _save(GenericEntity(GenericDao.TYPE_TABS, content))
+}
+
+suspend fun GenericDao.getTabs(): List<FbItem> = dao {
+ val allTabs = FbItem.values.map { it.name to it }.toMap()
+ _select(GenericDao.TYPE_TABS)
+ ?.split(",")
+ ?.mapNotNull { allTabs[it] }
+ ?.takeIf { it.isNotEmpty() }
+ ?: defaultTabs()
+}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt
new file mode 100644
index 00000000..d4b51c1e
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2018 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.db
+
+import androidx.room.ColumnInfo
+import androidx.room.Dao
+import androidx.room.Embedded
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.Index
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Transaction
+import com.pitchedapps.frost.services.NOTIF_CHANNEL_GENERAL
+import com.pitchedapps.frost.services.NOTIF_CHANNEL_MESSAGES
+import com.pitchedapps.frost.services.NotificationContent
+import com.pitchedapps.frost.utils.L
+import com.raizlabs.android.dbflow.annotation.ConflictAction
+import com.raizlabs.android.dbflow.annotation.Database
+import com.raizlabs.android.dbflow.annotation.Migration
+import com.raizlabs.android.dbflow.annotation.PrimaryKey
+import com.raizlabs.android.dbflow.annotation.Table
+import com.raizlabs.android.dbflow.kotlinextensions.eq
+import com.raizlabs.android.dbflow.kotlinextensions.from
+import com.raizlabs.android.dbflow.kotlinextensions.select
+import com.raizlabs.android.dbflow.kotlinextensions.where
+import com.raizlabs.android.dbflow.sql.SQLiteType
+import com.raizlabs.android.dbflow.sql.migration.AlterTableMigration
+import com.raizlabs.android.dbflow.structure.BaseModel
+
+@Entity(
+ tableName = "notifications",
+ primaryKeys = ["notif_id", "userId"],
+ foreignKeys = [ForeignKey(
+ entity = CookieEntity::class,
+ parentColumns = ["cookie_id"],
+ childColumns = ["userId"],
+ onDelete = ForeignKey.CASCADE
+ )],
+ indices = [Index("notif_id"), Index("userId")]
+)
+data class NotificationEntity(
+ @ColumnInfo(name = "notif_id")
+ val id: Long,
+ val userId: Long,
+ val href: String,
+ val title: String?,
+ val text: String,
+ val timestamp: Long,
+ val profileUrl: String?,
+ // Type essentially refers to channel
+ val type: String,
+ val unread: Boolean
+) {
+ constructor(
+ type: String,
+ content: NotificationContent
+ ) : this(
+ content.id,
+ content.data.id,
+ content.href,
+ content.title,
+ content.text,
+ content.timestamp,
+ content.profileUrl,
+ type,
+ content.unread
+ )
+}
+
+data class NotificationContentEntity(
+ @Embedded
+ val cookie: CookieEntity,
+ @Embedded
+ val notif: NotificationEntity
+) {
+ fun toNotifContent() = NotificationContent(
+ data = cookie,
+ id = notif.id,
+ href = notif.href,
+ title = notif.title,
+ text = notif.text,
+ timestamp = notif.timestamp,
+ profileUrl = notif.profileUrl,
+ unread = notif.unread
+ )
+}
+
+@Dao
+interface NotificationDao {
+
+ /**
+ * Note that notifications are guaranteed to be ordered by descending timestamp
+ */
+ @Transaction
+ @Query("SELECT * FROM cookies INNER JOIN notifications ON cookie_id = userId WHERE userId = :userId AND type = :type ORDER BY timestamp DESC")
+ fun _selectNotifications(userId: Long, type: String): List<NotificationContentEntity>
+
+ @Query("SELECT timestamp FROM notifications WHERE userId = :userId AND type = :type ORDER BY timestamp DESC LIMIT 1")
+ fun _selectEpoch(userId: Long, type: String): Long?
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun _insertNotifications(notifs: List<NotificationEntity>)
+
+ @Query("DELETE FROM notifications WHERE userId = :userId AND type = :type")
+ fun _deleteNotifications(userId: Long, type: String)
+
+ @Query("DELETE FROM notifications")
+ fun _deleteAll()
+
+ /**
+ * It is assumed that the notification batch comes from the same user
+ */
+ @Transaction
+ fun _saveNotifications(type: String, notifs: List<NotificationContent>) {
+ val userId = notifs.firstOrNull()?.data?.id ?: return
+ val entities = notifs.map { NotificationEntity(type, it) }
+ _deleteNotifications(userId, type)
+ _insertNotifications(entities)
+ }
+}
+
+suspend fun NotificationDao.deleteAll() = dao { _deleteAll() }
+
+fun NotificationDao.selectNotificationsSync(userId: Long, type: String): List<NotificationContent> =
+ _selectNotifications(userId, type).map { it.toNotifContent() }
+
+suspend fun NotificationDao.selectNotifications(userId: Long, type: String): List<NotificationContent> = dao {
+ selectNotificationsSync(userId, type)
+}
+
+/**
+ * Returns true if successful, given that there are constraints to the insertion
+ */
+suspend fun NotificationDao.saveNotifications(type: String, notifs: List<NotificationContent>): Boolean {
+ if (notifs.isEmpty()) return true
+ return dao {
+ try {
+ _saveNotifications(type, notifs)
+ true
+ } catch (e: Exception) {
+ L.e(e) { "Notif save failed for $type" }
+ false
+ }
+ }
+}
+
+suspend fun NotificationDao.latestEpoch(userId: Long, type: String): Long = dao {
+ _selectEpoch(userId, type) ?: lastNotificationTime(userId).let {
+ when (type) {
+ NOTIF_CHANNEL_GENERAL -> it.epoch
+ NOTIF_CHANNEL_MESSAGES -> it.epochIm
+ else -> -1L
+ }
+ }
+}
+
+/**
+ * Created by Allan Wang on 2017-05-30.
+ */
+
+@Database(version = NotificationDb.VERSION)
+object NotificationDb {
+ const val NAME = "Notifications"
+ const val VERSION = 2
+}
+
+@Migration(version = 2, database = NotificationDb::class)
+class NotificationMigration2(modelClass: Class<NotificationModel>) :
+ AlterTableMigration<NotificationModel>(modelClass) {
+ override fun onPreMigrate() {
+ super.onPreMigrate()
+ addColumn(SQLiteType.INTEGER, "epochIm")
+ L.d { "Added column" }
+ }
+}
+
+@Table(database = NotificationDb::class, allFields = true, primaryKeyConflict = ConflictAction.REPLACE)
+data class NotificationModel(
+ @PrimaryKey var id: Long = -1L,
+ var epoch: Long = -1L,
+ var epochIm: Long = -1L
+) : BaseModel()
+
+internal fun lastNotificationTime(id: Long): NotificationModel =
+ (select from NotificationModel::class where (NotificationModel_Table.id eq id)).querySingle()
+ ?: NotificationModel(id = id)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt
deleted file mode 100644
index 8411b8d7..00000000
--- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2018 Allan Wang
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package com.pitchedapps.frost.dbflow
-
-import android.os.Parcelable
-import com.pitchedapps.frost.utils.L
-import com.raizlabs.android.dbflow.annotation.ConflictAction
-import com.raizlabs.android.dbflow.annotation.Database
-import com.raizlabs.android.dbflow.annotation.PrimaryKey
-import com.raizlabs.android.dbflow.annotation.Table
-import com.raizlabs.android.dbflow.kotlinextensions.async
-import com.raizlabs.android.dbflow.kotlinextensions.delete
-import com.raizlabs.android.dbflow.kotlinextensions.eq
-import com.raizlabs.android.dbflow.kotlinextensions.from
-import com.raizlabs.android.dbflow.kotlinextensions.save
-import com.raizlabs.android.dbflow.kotlinextensions.select
-import com.raizlabs.android.dbflow.kotlinextensions.where
-import com.raizlabs.android.dbflow.structure.BaseModel
-import kotlinx.android.parcel.Parcelize
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-
-/**
- * Created by Allan Wang on 2017-05-30.
- */
-
-@Database(version = CookiesDb.VERSION)
-object CookiesDb {
- const val NAME = "Cookies"
- const val VERSION = 2
-}
-
-@Parcelize
-@Table(database = CookiesDb::class, allFields = true, primaryKeyConflict = ConflictAction.REPLACE)
-data class CookieModel(@PrimaryKey var id: Long = -1L, var name: String? = null, var cookie: String? = null) :
- BaseModel(), Parcelable {
-
- override fun toString(): String = "CookieModel(${hashCode()})"
-
- fun toSensitiveString(): String = "CookieModel(id=$id, name=$name, cookie=$cookie)"
-}
-
-fun loadFbCookie(id: Long): CookieModel? =
- (select from CookieModel::class where (CookieModel_Table.id eq id)).querySingle()
-
-fun loadFbCookie(name: String): CookieModel? =
- (select from CookieModel::class where (CookieModel_Table.name eq name)).querySingle()
-
-/**
- * Loads cookies sorted by name
- */
-fun loadFbCookiesAsync(callback: (cookies: List<CookieModel>) -> Unit) {
- (select from CookieModel::class).orderBy(CookieModel_Table.name, true).async()
- .queryListResultCallback { _, tResult -> callback(tResult) }.execute()
-}
-
-fun loadFbCookiesSync(): List<CookieModel> =
- (select from CookieModel::class).orderBy(CookieModel_Table.name, true).queryList()
-
-// TODO temp method until dbflow supports coroutines
-suspend fun loadFbCookiesSuspend(): List<CookieModel> = withContext(Dispatchers.IO) {
- loadFbCookiesSync()
-}
-
-inline fun saveFbCookie(cookie: CookieModel, crossinline callback: (() -> Unit) = {}) {
- cookie.async save {
- L.d { "Fb cookie saved" }
- L._d { cookie.toSensitiveString() }
- callback()
- }
-}
-
-fun removeCookie(id: Long) {
- loadFbCookie(id)?.async?.delete {
- L.d { "Fb cookie deleted" }
- L._d { id }
- }
-}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/DbUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/DbUtils.kt
deleted file mode 100644
index 740fef62..00000000
--- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/DbUtils.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2018 Allan Wang
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package com.pitchedapps.frost.dbflow
-
-import android.content.Context
-import com.pitchedapps.frost.utils.L
-import com.raizlabs.android.dbflow.config.FlowManager
-import com.raizlabs.android.dbflow.structure.database.transaction.FastStoreModelTransaction
-
-/**
- * Created by Allan Wang on 2017-05-30.
- */
-
-object DbUtils {
-
- fun db(name: String) = FlowManager.getDatabase(name)
- fun dbName(name: String) = "$name.db"
- fun deleteDatabase(c: Context, name: String) = c.deleteDatabase(dbName(name))
-}
-
-inline fun <reified T : Any> List<T>.replace(dbName: String) {
- L.d { "Replacing $dbName.db" }
- DbUtils.db(dbName).reset()
- FastStoreModelTransaction.saveBuilder(FlowManager.getModelAdapter(T::class.java)).addAll(this).build()
-}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/NotificationDb.kt
deleted file mode 100644
index a054d95e..00000000
--- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/NotificationDb.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2018 Allan Wang
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package com.pitchedapps.frost.dbflow
-
-import com.pitchedapps.frost.utils.L
-import com.raizlabs.android.dbflow.annotation.ConflictAction
-import com.raizlabs.android.dbflow.annotation.Database
-import com.raizlabs.android.dbflow.annotation.Migration
-import com.raizlabs.android.dbflow.annotation.PrimaryKey
-import com.raizlabs.android.dbflow.annotation.Table
-import com.raizlabs.android.dbflow.kotlinextensions.async
-import com.raizlabs.android.dbflow.kotlinextensions.eq
-import com.raizlabs.android.dbflow.kotlinextensions.from
-import com.raizlabs.android.dbflow.kotlinextensions.save
-import com.raizlabs.android.dbflow.kotlinextensions.select
-import com.raizlabs.android.dbflow.kotlinextensions.where
-import com.raizlabs.android.dbflow.sql.SQLiteType
-import com.raizlabs.android.dbflow.sql.migration.AlterTableMigration
-import com.raizlabs.android.dbflow.structure.BaseModel
-
-/**
- * Created by Allan Wang on 2017-05-30.
- */
-
-@Database(version = NotificationDb.VERSION)
-object NotificationDb {
- const val NAME = "Notifications"
- const val VERSION = 2
-}
-
-@Migration(version = 2, database = NotificationDb::class)
-class NotificationMigration2(modelClass: Class<NotificationModel>) :
- AlterTableMigration<NotificationModel>(modelClass) {
- override fun onPreMigrate() {
- super.onPreMigrate()
- addColumn(SQLiteType.INTEGER, "epochIm")
- L.d { "Added column" }
- }
-}
-
-@Table(database = NotificationDb::class, allFields = true, primaryKeyConflict = ConflictAction.REPLACE)
-data class NotificationModel(
- @PrimaryKey var id: Long = -1L,
- var epoch: Long = -1L,
- var epochIm: Long = -1L
-) : BaseModel()
-
-fun lastNotificationTime(id: Long): NotificationModel =
- (select from NotificationModel::class where (NotificationModel_Table.id eq id)).querySingle()
- ?: NotificationModel(id = id)
-
-fun saveNotificationTime(notificationModel: NotificationModel, callback: (() -> Unit)? = null) {
- notificationModel.async save {
- L.d { "Fb notification model saved" }
- L._d { notificationModel }
- callback?.invoke()
- }
-}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt
index 5683526a..0c1da3a3 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt
@@ -19,10 +19,12 @@ package com.pitchedapps.frost.facebook
import android.app.Activity
import android.content.Context
import android.webkit.CookieManager
-import com.pitchedapps.frost.dbflow.CookieModel
-import com.pitchedapps.frost.dbflow.loadFbCookie
-import com.pitchedapps.frost.dbflow.removeCookie
-import com.pitchedapps.frost.dbflow.saveFbCookie
+import com.pitchedapps.frost.db.CookieDao
+import com.pitchedapps.frost.db.CookieEntity
+import com.pitchedapps.frost.db.FrostDatabase
+import com.pitchedapps.frost.db.deleteById
+import com.pitchedapps.frost.db.save
+import com.pitchedapps.frost.db.selectById
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.cookies
@@ -50,6 +52,10 @@ object FbCookie {
inline val webCookie: String?
get() = CookieManager.getInstance().getCookie(COOKIE_DOMAIN)
+ private val cookieDao: CookieDao by lazy {
+ FrostDatabase.get().cookieDao()
+ }
+
private suspend fun CookieManager.suspendSetWebCookie(cookie: String?): Boolean {
cookie ?: return true
return withContext(NonCancellable) {
@@ -77,12 +83,12 @@ object FbCookie {
}
}
- fun save(id: Long) {
+ suspend fun save(id: Long) {
L.d { "New cookie found" }
Prefs.userId = id
CookieManager.getInstance().flush()
- val cookie = CookieModel(Prefs.userId, "", webCookie)
- saveFbCookie(cookie)
+ val cookie = CookieEntity(Prefs.userId, null, webCookie)
+ cookieDao.save(cookie)
}
suspend fun reset() {
@@ -93,11 +99,12 @@ object FbCookie {
}
}
- suspend fun switchUser(id: Long) = switchUser(loadFbCookie(id))
-
- suspend fun switchUser(name: String) = switchUser(loadFbCookie(name))
+ suspend fun switchUser(id: Long) {
+ val cookie = cookieDao.selectById(id) ?: return L.e { "No cookie for id" }
+ switchUser(cookie)
+ }
- suspend fun switchUser(cookie: CookieModel?) {
+ suspend fun switchUser(cookie: CookieEntity?) {
if (cookie == null) {
L.d { "Switching User; null cookie" }
return
@@ -114,7 +121,7 @@ object FbCookie {
* and launch the proper login page
*/
suspend fun logout(context: Context) {
- val cookies = arrayListOf<CookieModel>()
+ val cookies = arrayListOf<CookieEntity>()
if (context is Activity)
cookies.addAll(context.cookies().filter { it.id != Prefs.userId })
logout(Prefs.userId)
@@ -126,7 +133,9 @@ object FbCookie {
*/
suspend fun logout(id: Long) {
L.d { "Logging out user" }
- removeCookie(id)
+ cookieDao.deleteById(id)
+ L.d { "Fb cookie deleted" }
+ L._d { id }
reset()
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt
index 9ee34ab7..82e15111 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt
@@ -56,6 +56,9 @@ enum class FbItem(
PHOTOS(R.string.photos, GoogleMaterial.Icon.gmd_photo, "me/photos"),
PROFILE(R.string.profile, CommunityMaterial.Icon.cmd_account, "me"),
SAVED(R.string.saved, GoogleMaterial.Icon.gmd_bookmark, "saved"),
+ /**
+ * Note that this url only works if a query (?q=) is provided
+ */
_SEARCH(R.string.kau_search, GoogleMaterial.Icon.gmd_search, "search/top"),
SETTINGS(R.string.settings, GoogleMaterial.Icon.gmd_settings, "settings"),
;
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/FrostParser.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/FrostParser.kt
index 5709bb9f..24c39e28 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/FrostParser.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/FrostParser.kt
@@ -16,7 +16,7 @@
*/
package com.pitchedapps.frost.facebook.parsers
-import com.pitchedapps.frost.dbflow.CookieModel
+import com.pitchedapps.frost.db.CookieEntity
import com.pitchedapps.frost.facebook.FB_CSS_URL_MATCHER
import com.pitchedapps.frost.facebook.formattedFbUrl
import com.pitchedapps.frost.facebook.get
@@ -38,7 +38,7 @@ import org.jsoup.select.Elements
* The return type must be nonnull if no parsing errors occurred, as null signifies a parse error
* If null really must be allowed, use Optionals
*/
-interface FrostParser<out T : Any> {
+interface FrostParser<out T : ParseData> {
/**
* Name associated to parser
@@ -76,12 +76,16 @@ const val FALLBACK_TIME_MOD = 1000000
data class FrostLink(val text: String, val href: String)
-data class ParseResponse<out T>(val cookie: String, val data: T) {
+data class ParseResponse<out T: ParseData>(val cookie: String, val data: T) {
override fun toString() = "ParseResponse\ncookie: $cookie\ndata:\n$data"
}
-interface ParseNotification {
- fun getUnreadNotifications(data: CookieModel): List<NotificationContent>
+interface ParseData {
+ val isEmpty: Boolean
+}
+
+interface ParseNotification : ParseData {
+ fun getUnreadNotifications(data: CookieEntity): List<NotificationContent>
}
internal fun <T> List<T>.toJsonString(tag: String, indent: Int) = StringBuilder().apply {
@@ -95,7 +99,7 @@ internal fun <T> List<T>.toJsonString(tag: String, indent: Int) = StringBuilder(
* T should have a readable toString() function
* [redirectToText] dictates whether all data should be converted to text then back to document before parsing
*/
-internal abstract class FrostParserBase<out T : Any>(private val redirectToText: Boolean) : FrostParser<T> {
+internal abstract class FrostParserBase<out T : ParseData>(private val redirectToText: Boolean) : FrostParser<T> {
final override fun parse(cookie: String?) = parseFromUrl(cookie, url)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/MessageParser.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/MessageParser.kt
index f05c42e9..00a1432f 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/MessageParser.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/MessageParser.kt
@@ -16,7 +16,7 @@
*/
package com.pitchedapps.frost.facebook.parsers
-import com.pitchedapps.frost.dbflow.CookieModel
+import com.pitchedapps.frost.db.CookieEntity
import com.pitchedapps.frost.facebook.FB_EPOCH_MATCHER
import com.pitchedapps.frost.facebook.FB_MESSAGE_NOTIF_ID_MATCHER
import com.pitchedapps.frost.facebook.FbItem
@@ -46,6 +46,10 @@ data class FrostMessages(
val seeMore: FrostLink?,
val extraLinks: List<FrostLink>
) : ParseNotification {
+
+ override val isEmpty: Boolean
+ get() = threads.isEmpty()
+
override fun toString() = StringBuilder().apply {
append("FrostMessages {\n")
append(threads.toJsonString("threads", 1))
@@ -54,7 +58,7 @@ data class FrostMessages(
append("}")
}.toString()
- override fun getUnreadNotifications(data: CookieModel) =
+ override fun getUnreadNotifications(data: CookieEntity) =
threads.asSequence().filter(FrostThread::unread).map {
with(it) {
NotificationContent(
@@ -64,7 +68,8 @@ data class FrostMessages(
title = title,
text = content ?: "",
timestamp = time,
- profileUrl = img
+ profileUrl = img,
+ unread = unread
)
}
}.toList()
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/NotifParser.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/NotifParser.kt
index b8aa899b..faeaa27c 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/NotifParser.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/NotifParser.kt
@@ -16,7 +16,7 @@
*/
package com.pitchedapps.frost.facebook.parsers
-import com.pitchedapps.frost.dbflow.CookieModel
+import com.pitchedapps.frost.db.CookieEntity
import com.pitchedapps.frost.facebook.FB_EPOCH_MATCHER
import com.pitchedapps.frost.facebook.FB_NOTIF_ID_MATCHER
import com.pitchedapps.frost.facebook.FbItem
@@ -36,6 +36,10 @@ data class FrostNotifs(
val notifs: List<FrostNotif>,
val seeMore: FrostLink?
) : ParseNotification {
+
+ override val isEmpty: Boolean
+ get() = notifs.isEmpty()
+
override fun toString() = StringBuilder().apply {
append("FrostNotifs {\n")
append(notifs.toJsonString("notifs", 1))
@@ -43,7 +47,7 @@ data class FrostNotifs(
append("}")
}.toString()
- override fun getUnreadNotifications(data: CookieModel) =
+ override fun getUnreadNotifications(data: CookieEntity) =
notifs.asSequence().filter(FrostNotif::unread).map {
with(it) {
NotificationContent(
@@ -53,7 +57,8 @@ data class FrostNotifs(
title = null,
text = content,
timestamp = time,
- profileUrl = img
+ profileUrl = img,
+ unread = unread
)
}
}.toList()
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/SearchParser.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/SearchParser.kt
index 7869d881..b044ee58 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/SearchParser.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/SearchParser.kt
@@ -40,7 +40,10 @@ enum class SearchKeys(val key: String) {
EVENTS("keywords_events")
}
-data class FrostSearches(val results: List<FrostSearch>) {
+data class FrostSearches(val results: List<FrostSearch>) : ParseData {
+
+ override val isEmpty: Boolean
+ get() = results.isEmpty()
override fun toString() = StringBuilder().apply {
append("FrostSearches {\n")
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragmentBase.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragmentBase.kt
index ed6a4cf0..9f2d704c 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragmentBase.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragmentBase.kt
@@ -25,6 +25,7 @@ import com.mikepenz.fastadapter.adapters.ModelAdapter
import com.pitchedapps.frost.R
import com.pitchedapps.frost.facebook.FbCookie
import com.pitchedapps.frost.facebook.parsers.FrostParser
+import com.pitchedapps.frost.facebook.parsers.ParseData
import com.pitchedapps.frost.facebook.parsers.ParseResponse
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.frostJsoup
@@ -94,7 +95,7 @@ abstract class GenericRecyclerFragment<T, Item : IItem<*, *>> : RecyclerFragment
open fun getAdapter(): FastAdapter<IItem<*, *>> = fastAdapter(this.adapter)
}
-abstract class FrostParserFragment<T : Any, Item : IItem<*, *>> : RecyclerFragment<Item, Item>() {
+abstract class FrostParserFragment<T : ParseData, Item : IItem<*, *>> : RecyclerFragment<Item, Item>() {
/**
* The parser to make this all happen
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
index 54a9f8ae..1c37bc29 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
@@ -31,9 +31,10 @@ import ca.allanwang.kau.utils.string
import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.FrostWebActivity
-import com.pitchedapps.frost.dbflow.CookieModel
-import com.pitchedapps.frost.dbflow.NotificationModel
-import com.pitchedapps.frost.dbflow.lastNotificationTime
+import com.pitchedapps.frost.db.CookieEntity
+import com.pitchedapps.frost.db.FrostDatabase
+import com.pitchedapps.frost.db.latestEpoch
+import com.pitchedapps.frost.db.saveNotifications
import com.pitchedapps.frost.enums.OverlayContext
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.parsers.FrostParser
@@ -60,12 +61,10 @@ private val _40_DP = 40.dpToPx
* Enum to handle notification creations
*/
enum class NotificationType(
- private val channelId: String,
+ val channelId: String,
private val overlayContext: OverlayContext,
private val fbItem: FbItem,
private val parser: FrostParser<ParseNotification>,
- private val getTime: (notif: NotificationModel) -> Long,
- private val putTime: (notif: NotificationModel, time: Long) -> NotificationModel,
private val ringtone: () -> String
) {
@@ -74,8 +73,6 @@ enum class NotificationType(
OverlayContext.NOTIFICATION,
FbItem.NOTIFICATIONS,
NotifParser,
- NotificationModel::epoch,
- { notif, time -> notif.copy(epoch = time) },
Prefs::notificationRingtone
) {
@@ -88,8 +85,6 @@ enum class NotificationType(
OverlayContext.MESSAGE,
FbItem.MESSAGES,
MessageParser,
- NotificationModel::epochIm,
- { notif, time -> notif.copy(epochIm = time) },
Prefs::messageRingtone
);
@@ -100,8 +95,8 @@ enum class NotificationType(
*/
internal open fun bindRequest(content: NotificationContent, cookie: String): (BaseBundle.() -> Unit)? = null
- private fun bindRequest(intent: Intent, content: NotificationContent, cookie: String?) {
- cookie ?: return
+ private fun bindRequest(intent: Intent, content: NotificationContent) {
+ val cookie = content.data.cookie ?: return
val binder = bindRequest(content, cookie) ?: return
val bundle = Bundle()
bundle.binder()
@@ -116,7 +111,8 @@ enum class NotificationType(
* Returns the number of notifications generated,
* or -1 if an error occurred
*/
- fun fetch(context: Context, data: CookieModel): Int {
+ suspend fun fetch(context: Context, data: CookieEntity): Int {
+ val notifDao = FrostDatabase.get().notifDao()
val response = try {
parser.parse(data.cookie)
} catch (ignored: Exception) {
@@ -142,36 +138,42 @@ enum class NotificationType(
}
if (notifContents.isEmpty()) return 0
val userId = data.id
- val prevNotifTime = lastNotificationTime(userId)
- val prevLatestEpoch = getTime(prevNotifTime)
+ // Legacy, remove with dbflow
+ val prevLatestEpoch = notifDao.latestEpoch(userId, channelId)
L.v { "Notif $name prev epoch $prevLatestEpoch" }
- var newLatestEpoch = prevLatestEpoch
- val notifs = mutableListOf<FrostNotification>()
- notifContents.forEach { notif ->
- L.v { "Notif timestamp ${notif.timestamp}" }
- if (notif.timestamp <= prevLatestEpoch) return@forEach
- notifs.add(createNotification(context, notif))
- if (notif.timestamp > newLatestEpoch)
- newLatestEpoch = notif.timestamp
- }
- if (newLatestEpoch > prevLatestEpoch)
- putTime(prevNotifTime, newLatestEpoch).save()
- L.d { "Notif $name new epoch ${getTime(lastNotificationTime(userId))}" }
if (prevLatestEpoch == -1L && !BuildConfig.DEBUG) {
L.d { "Skipping first notification fetch" }
return 0 // do not notify the first time
}
+
+ val newNotifContents = notifContents.filter { it.timestamp > prevLatestEpoch }
+
+ if (newNotifContents.isEmpty()) {
+ L.d { "No new notifs found for $name" }
+ return 0
+ }
+
+ L.d { "${newNotifContents.size} new notifs found for $name" }
+
+ if (!notifDao.saveNotifications(channelId, newNotifContents)) {
+ L.d { "Skip notifs for $name as saving failed" }
+ return 0
+ }
+
+ val notifs = newNotifContents.map { createNotification(context, it) }
+
frostEvent("Notifications", "Type" to name, "Count" to notifs.size)
if (notifs.size > 1)
summaryNotification(context, userId, notifs.size).notify(context)
val ringtone = ringtone()
notifs.forEachIndexed { i, notif ->
+ // Ring at most twice
notif.withAlert(i < 2, ringtone).notify(context)
}
return notifs.size
}
- fun debugNotification(context: Context, data: CookieModel) {
+ fun debugNotification(context: Context, data: CookieEntity) {
val content = NotificationContent(
data,
System.currentTimeMillis(),
@@ -179,23 +181,40 @@ enum class NotificationType(
"Debug Notif",
"Test 123",
System.currentTimeMillis() / 1000,
- "https://www.iconexperience.com/_img/v_collection_png/256x256/shadow/dog.png"
+ "https://www.iconexperience.com/_img/v_collection_png/256x256/shadow/dog.png",
+ false
)
createNotification(context, content).notify(context)
}
/**
+ * Attach content related data to an intent
+ */
+ fun putContentExtra(intent: Intent, content: NotificationContent): Intent {
+ // We will show the notification page for dependent urls. We can trigger a click next time
+ intent.data = Uri.parse(if (content.href.isIndependent) content.href else FbItem.NOTIFICATIONS.url)
+ bindRequest(intent, content)
+ return intent
+ }
+
+ /**
+ * Create a generic content for the provided type and user id.
+ * No content related data is added
+ */
+ fun createCommonIntent(context: Context, userId: Long): Intent {
+ val intent = Intent(context, FrostWebActivity::class.java)
+ intent.putExtra(ARG_USER_ID, userId)
+ overlayContext.put(intent)
+ return intent
+ }
+
+ /**
* Create and submit a new notification with the given [content]
*/
private fun createNotification(context: Context, content: NotificationContent): FrostNotification =
with(content) {
- val intent = Intent(context, FrostWebActivity::class.java)
- // TODO temp fix; we will show notification page for dependent urls. We can trigger a click next time
- intent.data = Uri.parse(if (href.isIndependent) href else FbItem.NOTIFICATIONS.url)
- intent.putExtra(ARG_USER_ID, data.id)
- overlayContext.put(intent)
- bindRequest(intent, content, data.cookie)
-
+ val intent = createCommonIntent(context, content.data.id)
+ putContentExtra(intent, content)
val group = "${groupPrefix}_${data.id}"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val notifBuilder = context.frostNotification(channelId)
@@ -257,13 +276,15 @@ enum class NotificationType(
* Notification data holder
*/
data class NotificationContent(
- val data: CookieModel,
+ // TODO replace data with userId?
+ val data: CookieEntity,
val id: Long,
val href: String,
val title: String? = null, // defaults to frost title
val text: String,
val timestamp: Long,
- val profileUrl: String?
+ val profileUrl: String?,
+ val unread: Boolean
) {
val notifId = Math.abs(id.toInt())
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
index b1e0ac8c..0eee5558 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
@@ -21,16 +21,19 @@ import androidx.core.app.NotificationManagerCompat
import ca.allanwang.kau.utils.string
import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
-import com.pitchedapps.frost.dbflow.CookieModel
-import com.pitchedapps.frost.dbflow.loadFbCookiesSync
+import com.pitchedapps.frost.db.CookieDao
+import com.pitchedapps.frost.db.CookieEntity
+import com.pitchedapps.frost.db.selectAll
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostEvent
+import com.pitchedapps.frost.widgets.NotificationWidget
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
+import org.koin.android.ext.android.inject
/**
* Created by Allan Wang on 2017-06-14.
@@ -42,6 +45,8 @@ import kotlinx.coroutines.yield
*/
class NotificationService : BaseJobService() {
+ val cookieDao: CookieDao by inject()
+
override fun onStopJob(params: JobParameters?): Boolean {
super.onStopJob(params)
prepareFinish(true)
@@ -81,7 +86,7 @@ class NotificationService : BaseJobService() {
private suspend fun sendNotifications(params: JobParameters?): Unit = withContext(Dispatchers.Default) {
val currentId = Prefs.userId
- val cookies = loadFbCookiesSync()
+ val cookies = cookieDao.selectAll()
yield()
val jobId = params?.extras?.getInt(NOTIFICATION_PARAM_ID, -1) ?: -1
var notifCount = 0
@@ -101,13 +106,16 @@ class NotificationService : BaseJobService() {
L.i { "Sent $notifCount notifications" }
if (notifCount == 0 && jobId == NOTIFICATION_JOB_NOW)
generalNotification(665, R.string.no_new_notifications, BuildConfig.DEBUG)
+ if (notifCount > 0) {
+ NotificationWidget.forceUpdate(this@NotificationService)
+ }
}
/**
* Implemented fetch to also notify when an error occurs
* Also normalized the output to return the number of notifications received
*/
- private fun fetch(jobId: Int, type: NotificationType, cookie: CookieModel): Int {
+ private suspend fun fetch(jobId: Int, type: NotificationType, cookie: CookieEntity): Int {
val count = type.fetch(this, cookie)
if (count < 0) {
if (jobId == NOTIFICATION_JOB_NOW)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt
index 0200f109..c58710b5 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt
@@ -29,14 +29,15 @@ import ca.allanwang.kau.utils.string
import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.SettingsActivity
-import com.pitchedapps.frost.dbflow.NotificationModel
-import com.pitchedapps.frost.dbflow.loadFbCookiesAsync
+import com.pitchedapps.frost.db.FrostDatabase
+import com.pitchedapps.frost.db.deleteAll
import com.pitchedapps.frost.services.fetchNotifications
import com.pitchedapps.frost.services.scheduleNotifications
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostSnackbar
import com.pitchedapps.frost.utils.materialDialogThemed
import com.pitchedapps.frost.views.Keywords
+import kotlinx.coroutines.launch
/**
* Created by Allan Wang on 2017-06-29.
@@ -171,8 +172,8 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
if (BuildConfig.DEBUG) {
plainText(R.string.reset_notif_epoch) {
onClick = {
- loadFbCookiesAsync { cookies ->
- cookies.map { NotificationModel(it.id) }.forEach { it.save() }
+ launch {
+ FrostDatabase.get().notifDao().deleteAll()
}
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
index a8dc11f4..50863e10 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
@@ -29,7 +29,7 @@ import ca.allanwang.kau.utils.showAppInfo
import ca.allanwang.kau.utils.string
import ca.allanwang.kau.utils.toast
import com.pitchedapps.frost.R
-import com.pitchedapps.frost.dbflow.loadFbCookie
+import com.pitchedapps.frost.db.CookieEntity
import com.pitchedapps.frost.facebook.USER_AGENT_DESKTOP
/**
@@ -38,6 +38,7 @@ import com.pitchedapps.frost.facebook.USER_AGENT_DESKTOP
* With reference to <a href="https://stackoverflow.com/questions/33434532/android-webview-download-files-like-browsers-do">Stack Overflow</a>
*/
fun Context.frostDownload(
+ cookie: CookieEntity,
url: String?,
userAgent: String = USER_AGENT_DESKTOP,
contentDisposition: String? = null,
@@ -45,10 +46,11 @@ fun Context.frostDownload(
contentLength: Long = 0L
) {
url ?: return
- frostDownload(Uri.parse(url), userAgent, contentDisposition, mimeType, contentLength)
+ frostDownload(cookie, Uri.parse(url), userAgent, contentDisposition, mimeType, contentLength)
}
fun Context.frostDownload(
+ cookie: CookieEntity,
uri: Uri?,
userAgent: String = USER_AGENT_DESKTOP,
contentDisposition: String? = null,
@@ -75,7 +77,6 @@ fun Context.frostDownload(
if (!granted) return@kauRequestPermissions
val request = DownloadManager.Request(uri)
request.setMimeType(mimeType)
- val cookie = loadFbCookie(Prefs.userId) ?: return@kauRequestPermissions
val title = URLUtil.guessFileName(uri.toString(), contentDisposition, mimeType)
request.addRequestHeader("Cookie", cookie.cookie)
request.addRequestHeader("User-Agent", userAgent)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt
index 8364c34e..7c8c1895 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt
@@ -50,6 +50,11 @@ object L : KauLogger("Frost", {
d(message)
}
+ inline fun _e(e: Throwable?, message: () -> Any?) {
+ if (BuildConfig.DEBUG)
+ e(e, message)
+ }
+
override fun logImpl(priority: Int, message: String?, t: Throwable?) {
if (BuildConfig.DEBUG)
super.logImpl(priority, message, t)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/TimeUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/TimeUtils.kt
new file mode 100644
index 00000000..c0feab1e
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/TimeUtils.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.utils
+
+import android.content.Context
+import ca.allanwang.kau.utils.string
+import com.pitchedapps.frost.R
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Date
+import java.util.Locale
+
+/**
+ * Converts time in millis to readable date,
+ * eg Apr 24 at 7:32 PM
+ *
+ * With regards to date modifications in calendars,
+ * it appears to respect calendar rules;
+ * see https://stackoverflow.com/a/43227817/4407321
+ */
+fun Long.toReadableTime(context: Context): String {
+ val cal = Calendar.getInstance()
+ cal.timeInMillis = this
+ val timeFormatter = SimpleDateFormat.getTimeInstance(DateFormat.SHORT)
+ val time = timeFormatter.format(Date(this))
+ val day = when {
+ cal >= Calendar.getInstance().apply { add(Calendar.DAY_OF_MONTH, -1) } -> context.string(R.string.today)
+ cal >= Calendar.getInstance().apply { add(Calendar.DAY_OF_MONTH, -2) } -> context.string(R.string.yesterday)
+ else -> {
+ val dayFormatter = SimpleDateFormat("MMM dd", Locale.getDefault())
+ dayFormatter.format(Date(this))
+ }
+ }
+ return context.getString(R.string.time_template, day, time)
+}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
index 7f9ac98b..76ffd8cd 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
@@ -62,7 +62,7 @@ import com.pitchedapps.frost.activities.TabCustomizerActivity
import com.pitchedapps.frost.activities.WebOverlayActivity
import com.pitchedapps.frost.activities.WebOverlayActivityBase
import com.pitchedapps.frost.activities.WebOverlayDesktopActivity
-import com.pitchedapps.frost.dbflow.CookieModel
+import com.pitchedapps.frost.db.CookieEntity
import com.pitchedapps.frost.facebook.FACEBOOK_COM
import com.pitchedapps.frost.facebook.FBCDN_NET
import com.pitchedapps.frost.facebook.FbCookie
@@ -103,7 +103,7 @@ internal inline val Context.ctxCoroutine: CoroutineScope
get() = this as? CoroutineScope ?: GlobalScope
inline fun <reified T : Activity> Context.launchNewTask(
- cookieList: ArrayList<CookieModel> = arrayListOf(),
+ cookieList: ArrayList<CookieEntity> = arrayListOf(),
clearStack: Boolean = false
) {
startActivity<T>(clearStack, intentBuilder = {
@@ -111,13 +111,13 @@ inline fun <reified T : Activity> Context.launchNewTask(
})
}
-fun Context.launchLogin(cookieList: ArrayList<CookieModel>, clearStack: Boolean = true) {
+fun Context.launchLogin(cookieList: ArrayList<CookieEntity>, clearStack: Boolean = true) {
if (cookieList.isNotEmpty()) launchNewTask<SelectorActivity>(cookieList, clearStack)
else launchNewTask<LoginActivity>(clearStack = clearStack)
}
-fun Activity.cookies(): ArrayList<CookieModel> {
- return intent?.getParcelableArrayListExtra<CookieModel>(EXTRA_COOKIES) ?: arrayListOf()
+fun Activity.cookies(): ArrayList<CookieEntity> {
+ return intent?.getParcelableArrayListExtra<CookieEntity>(EXTRA_COOKIES) ?: arrayListOf()
}
/**
@@ -186,7 +186,7 @@ fun MaterialDialog.Builder.theme(): MaterialDialog.Builder {
}
fun Activity.setFrostTheme(forceTransparent: Boolean = false) {
- val isTransparent = (Color.alpha(Prefs.bgColor) != 255) || forceTransparent
+ val isTransparent = (Color.alpha(Prefs.bgColor) != 255) || (Color.alpha(Prefs.headerColor) != 255) || forceTransparent
if (Prefs.bgColor.isColorDark)
setTheme(if (isTransparent) R.style.FrostTheme_Transparent else R.style.FrostTheme)
else
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt
index d60ea7ed..0269b1a9 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt
@@ -33,7 +33,7 @@ import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.R
-import com.pitchedapps.frost.dbflow.CookieModel
+import com.pitchedapps.frost.db.CookieEntity
import com.pitchedapps.frost.facebook.profilePictureUrl
import com.pitchedapps.frost.glide.FrostGlide
import com.pitchedapps.frost.glide.GlideApp
@@ -42,7 +42,7 @@ import com.pitchedapps.frost.utils.Prefs
/**
* Created by Allan Wang on 2017-06-05.
*/
-class AccountItem(val cookie: CookieModel?) : KauIItem<AccountItem, AccountItem.ViewHolder>
+class AccountItem(val cookie: CookieEntity?) : KauIItem<AccountItem, AccountItem.ViewHolder>
(R.layout.view_account, { ViewHolder(it) }, R.id.item_account) {
override fun bindView(viewHolder: ViewHolder, payloads: MutableList<Any>) {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt
index c2535940..4d88ad3d 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt
@@ -32,6 +32,7 @@ import ca.allanwang.kau.utils.inflate
import ca.allanwang.kau.utils.isColorDark
import ca.allanwang.kau.utils.isGone
import ca.allanwang.kau.utils.isVisible
+import ca.allanwang.kau.utils.launchMain
import ca.allanwang.kau.utils.setIcon
import ca.allanwang.kau.utils.setMenuIcons
import ca.allanwang.kau.utils.visible
@@ -39,8 +40,11 @@ import ca.allanwang.kau.utils.withMinAlpha
import com.devbrackets.android.exomedia.listener.VideoControlsVisibilityListener
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.R
+import com.pitchedapps.frost.db.FrostDatabase
+import com.pitchedapps.frost.db.currentCookie
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
+import com.pitchedapps.frost.utils.ctxCoroutine
import com.pitchedapps.frost.utils.frostDownload
import kotlinx.android.synthetic.main.view_video.view.*
@@ -96,7 +100,10 @@ class FrostVideoViewer @JvmOverloads constructor(
video_toolbar.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_pip -> video.isExpanded = false
- R.id.action_download -> context.frostDownload(video.videoUri)
+ R.id.action_download -> context.ctxCoroutine.launchMain {
+ val cookie = FrostDatabase.get().cookieDao().currentCookie() ?: return@launchMain
+ context.frostDownload(cookie, video.videoUri)
+ }
}
true
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
index cc8e3fbc..1dd027fd 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
@@ -24,15 +24,19 @@ import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import ca.allanwang.kau.utils.AnimHolder
+import ca.allanwang.kau.utils.launchMain
import com.pitchedapps.frost.contracts.FrostContentContainer
import com.pitchedapps.frost.contracts.FrostContentCore
import com.pitchedapps.frost.contracts.FrostContentParent
+import com.pitchedapps.frost.db.FrostDatabase
+import com.pitchedapps.frost.db.currentCookie
import com.pitchedapps.frost.facebook.FB_HOME_URL
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.USER_AGENT_DESKTOP
import com.pitchedapps.frost.facebook.USER_AGENT_MOBILE
import com.pitchedapps.frost.fragments.WebFragment
import com.pitchedapps.frost.utils.Prefs
+import com.pitchedapps.frost.utils.ctxCoroutine
import com.pitchedapps.frost.utils.frostDownload
import com.pitchedapps.frost.web.FrostChromeClient
import com.pitchedapps.frost.web.FrostJSI
@@ -81,7 +85,13 @@ class FrostWebView @JvmOverloads constructor(
webChromeClient = FrostChromeClient(this)
addJavascriptInterface(FrostJSI(this), "Frost")
setBackgroundColor(Color.TRANSPARENT)
- setDownloadListener(context::frostDownload)
+ val db = FrostDatabase.get()
+ setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
+ context.ctxCoroutine.launchMain {
+ val cookie = db.cookieDao().currentCookie() ?: return@launchMain
+ context.frostDownload(cookie, url, userAgent, contentDisposition, mimetype, contentLength)
+ }
+ }
return this
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/DebugWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/DebugWebView.kt
index 6511ef9f..c66180ed 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/DebugWebView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/DebugWebView.kt
@@ -58,6 +58,7 @@ class DebugWebView @JvmOverloads constructor(
settings.userAgentString = USER_AGENT_MOBILE
setLayerType(View.LAYER_TYPE_HARDWARE, null)
webViewClient = DebugClient()
+ @Suppress("DEPRECATION")
isDrawingCacheEnabled = true
}
@@ -72,6 +73,7 @@ class DebugWebView @JvmOverloads constructor(
}
try {
output.outputStream().use {
+ @Suppress("DEPRECATION")
drawingCache.compress(Bitmap.CompressFormat.PNG, 100, it)
}
L.d { "Created screenshot at ${output.absolutePath}" }
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
index 50a5e2e1..0d980ba0 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
@@ -21,7 +21,7 @@ import android.webkit.JavascriptInterface
import com.pitchedapps.frost.activities.MainActivity
import com.pitchedapps.frost.contracts.MainActivityContract
import com.pitchedapps.frost.contracts.VideoViewHolder
-import com.pitchedapps.frost.dbflow.CookieModel
+import com.pitchedapps.frost.db.CookieEntity
import com.pitchedapps.frost.facebook.FbCookie
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
@@ -44,7 +44,7 @@ class FrostJSI(val web: FrostWebView) {
private val activity: MainActivity? = context as? MainActivity
private val header: SendChannel<String>? = activity?.headerBadgeChannel
private val refresh: SendChannel<Boolean> = web.parent.refreshChannel
- private val cookies: List<CookieModel> = activity?.cookies() ?: arrayListOf()
+ private val cookies: List<CookieEntity> = activity?.cookies() ?: arrayListOf()
/**
* Attempts to load the url in an overlay
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
index c27385fc..79c6d5ba 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
@@ -29,7 +29,7 @@ import android.webkit.WebView
import ca.allanwang.kau.utils.fadeIn
import ca.allanwang.kau.utils.isVisible
import ca.allanwang.kau.utils.launchMain
-import com.pitchedapps.frost.dbflow.CookieModel
+import com.pitchedapps.frost.db.CookieEntity
import com.pitchedapps.frost.facebook.FB_LOGIN_URL
import com.pitchedapps.frost.facebook.FB_USER_MATCHER
import com.pitchedapps.frost.facebook.FbCookie
@@ -51,7 +51,7 @@ class LoginWebView @JvmOverloads constructor(
defStyleAttr: Int = 0
) : WebView(context, attrs, defStyleAttr) {
- private val completable: CompletableDeferred<CookieModel> = CompletableDeferred()
+ private val completable: CompletableDeferred<CookieEntity> = CompletableDeferred()
private lateinit var progressCallback: (Int) -> Unit
@SuppressLint("SetJavaScriptEnabled")
@@ -62,7 +62,7 @@ class LoginWebView @JvmOverloads constructor(
webChromeClient = LoginChromeClient()
}
- suspend fun loadLogin(progressCallback: (Int) -> Unit): CompletableDeferred<CookieModel> = coroutineScope {
+ suspend fun loadLogin(progressCallback: (Int) -> Unit): CompletableDeferred<CookieEntity> = coroutineScope {
this@LoginWebView.progressCallback = progressCallback
L.d { "Begin loading login" }
launchMain {
@@ -77,18 +77,18 @@ class LoginWebView @JvmOverloads constructor(
override fun onPageFinished(view: WebView, url: String?) {
super.onPageFinished(view, url)
- val cookieModel = checkForLogin(url)
- if (cookieModel != null)
- completable.complete(cookieModel)
+ val cookie = checkForLogin(url)
+ if (cookie != null)
+ completable.complete(cookie)
if (!view.isVisible) view.fadeIn()
}
- fun checkForLogin(url: String?): CookieModel? {
+ fun checkForLogin(url: String?): CookieEntity? {
if (!url.isFacebookUrl) return null
val cookie = CookieManager.getInstance().getCookie(url) ?: return null
L.d { "Checking cookie for login" }
val id = FB_USER_MATCHER.find(cookie)[1]?.toLong() ?: return null
- return CookieModel(id, "", cookie)
+ return CookieEntity(id, null, cookie)
}
override fun onPageCommitVisible(view: WebView, url: String?) {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt
new file mode 100644
index 00000000..594da00a
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2019 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.widgets
+
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProvider
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.graphics.drawable.Icon
+import android.os.Build
+import android.widget.RemoteViews
+import android.widget.RemoteViewsService
+import androidx.annotation.ColorInt
+import androidx.annotation.DrawableRes
+import androidx.annotation.IdRes
+import ca.allanwang.kau.utils.dimenPixelSize
+import ca.allanwang.kau.utils.withAlpha
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.activities.MainActivity
+import com.pitchedapps.frost.db.NotificationDao
+import com.pitchedapps.frost.db.selectNotificationsSync
+import com.pitchedapps.frost.glide.FrostGlide
+import com.pitchedapps.frost.glide.GlideApp
+import com.pitchedapps.frost.services.NotificationContent
+import com.pitchedapps.frost.services.NotificationType
+import com.pitchedapps.frost.utils.Prefs
+import com.pitchedapps.frost.utils.toReadableTime
+import org.koin.standalone.KoinComponent
+import org.koin.standalone.inject
+
+class NotificationWidget : AppWidgetProvider() {
+
+ override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
+ super.onUpdate(context, appWidgetManager, appWidgetIds)
+ val type = NotificationType.GENERAL
+ val userId = Prefs.userId
+ val intent = NotificationWidgetService.createIntent(context, type, userId)
+ for (id in appWidgetIds) {
+ val views = RemoteViews(context.packageName, R.layout.widget_notifications)
+
+ views.setBackgroundColor(R.id.widget_layout_toolbar, Prefs.headerColor)
+ views.setIcon(R.id.img_frost, context, R.drawable.frost_f_24, Prefs.iconColor)
+ views.setOnClickPendingIntent(
+ R.id.img_frost,
+ PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), 0)
+ )
+
+ views.setBackgroundColor(R.id.widget_notification_list, Prefs.bgColor)
+ views.setRemoteAdapter(R.id.widget_notification_list, intent)
+
+ val pendingIntentTemplate = PendingIntent.getActivity(
+ context,
+ 0,
+ type.createCommonIntent(context, userId),
+ PendingIntent.FLAG_UPDATE_CURRENT
+ )
+
+ views.setPendingIntentTemplate(R.id.widget_notification_list, pendingIntentTemplate)
+
+ appWidgetManager.updateAppWidget(id, views)
+ }
+ appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.widget_notification_list)
+ }
+
+ companion object {
+ fun forceUpdate(context: Context) {
+ val manager = AppWidgetManager.getInstance(context)
+ val ids = manager.getAppWidgetIds(ComponentName(context, NotificationWidget::class.java))
+ val intent = Intent().apply {
+ action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
+ }
+ context.sendBroadcast(intent)
+ }
+ }
+}
+
+private const val NOTIF_WIDGET_TYPE = "notif_widget_type"
+private const val NOTIF_WIDGET_USER_ID = "notif_widget_user_id"
+
+private fun RemoteViews.setBackgroundColor(@IdRes viewId: Int, @ColorInt color: Int) {
+ setInt(viewId, "setBackgroundColor", color)
+}
+
+/**
+ * Adds backward compatibility to setting tinted icons
+ */
+private fun RemoteViews.setIcon(@IdRes viewId: Int, context: Context, @DrawableRes res: Int, @ColorInt color: Int) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val icon = Icon.createWithResource(context, res).setTint(color).setTintMode(PorterDuff.Mode.SRC_IN)
+ setImageViewIcon(viewId, icon)
+ } else {
+ val bitmap = BitmapFactory.decodeResource(context.resources, res)
+ if (bitmap != null) {
+ val paint = Paint()
+ paint.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
+ val result = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(result)
+ canvas.drawBitmap(bitmap, 0f, 0f, paint)
+ setImageViewBitmap(viewId, result)
+ } else {
+ // Fallback to just icon
+ setImageViewResource(viewId, res)
+ }
+ }
+}
+
+class NotificationWidgetService : RemoteViewsService() {
+ override fun onGetViewFactory(intent: Intent): RemoteViewsFactory = NotificationWidgetDataProvider(this, intent)
+
+ companion object {
+ fun createIntent(context: Context, type: NotificationType, userId: Long): Intent =
+ Intent(context, NotificationWidgetService::class.java)
+ .putExtra(NOTIF_WIDGET_TYPE, type.name)
+ .putExtra(NOTIF_WIDGET_USER_ID, userId)
+ }
+}
+
+class NotificationWidgetDataProvider(val context: Context, val intent: Intent) : RemoteViewsService.RemoteViewsFactory,
+ KoinComponent {
+
+ private val notifDao: NotificationDao by inject()
+ @Volatile
+ private var content: List<NotificationContent> = emptyList()
+
+ private val type = NotificationType.valueOf(intent.getStringExtra(NOTIF_WIDGET_TYPE))
+
+ private val userId = intent.getLongExtra(NOTIF_WIDGET_USER_ID, -1)
+
+ private val avatarSize = context.dimenPixelSize(R.dimen.avatar_image_size)
+
+ private val glide = GlideApp.with(context).asBitmap()
+
+ private fun loadNotifications() {
+ content = notifDao.selectNotificationsSync(userId, type.channelId)
+ }
+
+ override fun onCreate() {
+ }
+
+ override fun onDataSetChanged() {
+ loadNotifications()
+ }
+
+ override fun getLoadingView(): RemoteViews? = null
+
+ override fun getItemId(position: Int): Long = content[position].id
+
+ override fun hasStableIds(): Boolean = true
+
+ override fun getViewAt(position: Int): RemoteViews {
+ val views = RemoteViews(context.packageName, R.layout.widget_notification_item)
+ val notif = content[position]
+ views.setBackgroundColor(R.id.item_frame, Prefs.nativeBgColor(notif.unread))
+ views.setTextColor(R.id.item_content, Prefs.textColor)
+ views.setTextViewText(R.id.item_content, notif.text)
+ views.setTextColor(R.id.item_date, Prefs.textColor.withAlpha(150))
+ views.setTextViewText(R.id.item_date, notif.timestamp.toReadableTime(context))
+
+ val avatar = glide.load(notif.profileUrl).transform(FrostGlide.circleCrop).submit(avatarSize, avatarSize).get()
+ views.setImageViewBitmap(R.id.item_avatar, avatar)
+ views.setOnClickFillInIntent(R.id.item_frame, type.putContentExtra(Intent(), notif))
+ return views
+ }
+
+ override fun getCount(): Int = content.size
+
+ override fun getViewTypeCount(): Int = 1
+
+ override fun onDestroy() {
+ }
+}
diff --git a/app/src/main/res/drawable/notification_widget_preview.xml b/app/src/main/res/drawable/notification_widget_preview.xml
new file mode 100644
index 00000000..a03fd362
--- /dev/null
+++ b/app/src/main/res/drawable/notification_widget_preview.xml
@@ -0,0 +1,48 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="30dp"
+ android:height="40dp"
+ android:viewportWidth="300"
+ android:viewportHeight="400">
+ <path
+ android:pathData="M0,0h300v400H0V0z"
+ android:fillColor="#fafafa"/>
+ <path
+ android:pathData="M0,0h300v50H0V0z"
+ android:fillColor="@color/facebook_blue"/>
+ <path
+ android:pathData="M65,170a20,20 0,1 1,-40 0,20 20,0 0,1 40,0z"
+ android:fillColor="#DE000000"/>
+ <path
+ android:pathData="M85,150h184v11H85v-11z"
+ android:fillColor="#DE000000"/>
+ <path
+ android:pathData="M85,179h146v11H85v-11z"
+ android:fillColor="#DE000000"/>
+ <path
+ android:pathData="M65,95a20,20 0,1 1,-40 0,20 20,0 0,1 40,0z"
+ android:fillColor="#DE000000"/>
+ <path
+ android:pathData="M85,75h184v11H85V75z"
+ android:fillColor="#DE000000"/>
+ <path
+ android:pathData="M85,104h146v11H85v-11z"
+ android:fillColor="#DE000000"/>
+ <path
+ android:pathData="M65,245a20,20 0,1 1,-40 0,20 20,0 0,1 40,0z"
+ android:fillColor="#DE000000"/>
+ <path
+ android:pathData="M85,225h184v11H85v-11z"
+ android:fillColor="#DE000000"/>
+ <path
+ android:pathData="M85,254h146v11H85v-11z"
+ android:fillColor="#DE000000"/>
+ <path
+ android:pathData="M65,320a20,20 0,1 1,-40 0,20 20,0 0,1 40,0z"
+ android:fillColor="#DE000000"/>
+ <path
+ android:pathData="M85,300h184v11H85v-11z"
+ android:fillColor="#DE000000"/>
+ <path
+ android:pathData="M85,329h146v11H85v-11z"
+ android:fillColor="#DE000000"/>
+</vector>
diff --git a/app/src/main/res/layout/widget_notification_item.xml b/app/src/main/res/layout/widget_notification_item.xml
new file mode 100644
index 00000000..f36f2766
--- /dev/null
+++ b/app/src/main/res/layout/widget_notification_item.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/item_frame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:selectableItemBackground"
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/kau_activity_horizontal_margin"
+ android:paddingTop="@dimen/kau_activity_vertical_margin"
+ android:paddingEnd="@dimen/kau_activity_horizontal_margin"
+ android:paddingBottom="@dimen/kau_activity_vertical_margin">
+
+ <ImageView
+ android:id="@+id/item_avatar"
+ android:layout_width="@dimen/avatar_image_size"
+ android:layout_height="@dimen/avatar_image_size" />
+
+ <!--
+ Unlike the actual notification panel,
+ we do not show thumbnails, and we limit the title length
+ -->
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/kau_padding_normal"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/item_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:lines="2" />
+
+ <TextView
+ android:id="@+id/item_date"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:lines="1"
+ android:textSize="12sp" />
+
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/widget_notifications.xml b/app/src/main/res/layout/widget_notifications.xml
new file mode 100644
index 00000000..4c42be85
--- /dev/null
+++ b/app/src/main/res/layout/widget_notifications.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/widget_layout_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/widget_layout_toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/kau_padding_small"
+ android:paddingEnd="@dimen/kau_padding_small">
+
+ <ImageView
+ android:id="@+id/img_frost"
+ android:layout_width="@dimen/toolbar_icon_size"
+ android:layout_height="@dimen/toolbar_icon_size"
+ android:layout_gravity="center_vertical"
+ android:layout_margin="@dimen/kau_padding_small"
+ android:background="?android:selectableItemBackgroundBorderless" />
+
+ </LinearLayout>
+
+ <ListView
+ android:id="@+id/widget_notification_list"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml
index 8d8d7c1a..357c7fd7 100644
--- a/app/src/main/res/values-cs-rCZ/strings.xml
+++ b/app/src/main/res/values-cs-rCZ/strings.xml
@@ -46,4 +46,12 @@ plně funkční náhrada za oficiální aplikaci Facebooku, vytvořena od nuly a
<string name="options">Nastavení</string>
<string name="tab_customizer_instructions">Dlouhým stiskem přeuspořádejte horní ikony.</string>
<string name="no_new_notifications">Žádné nové oznámení</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml
index 4d154d06..fc7a0f44 100644
--- a/app/src/main/res/values-da-rDK/strings.xml
+++ b/app/src/main/res/values-da-rDK/strings.xml
@@ -45,4 +45,12 @@
<string name="options">Valgmuligheder</string>
<string name="tab_customizer_instructions">Hold nede og træk for at flytte de øverste ikoner.</string>
<string name="no_new_notifications">Ingen nye notifikationer</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml
index f87447c4..84b9ae8d 100644
--- a/app/src/main/res/values-de-rDE/strings.xml
+++ b/app/src/main/res/values-de-rDE/strings.xml
@@ -17,6 +17,7 @@
<string name="birthdays">Geburtstage</string>
<string name="chat">Chat</string>
<string name="photos">Fotos</string>
+ <string name="marketplace">Marktplatz</string>
<string name="notes">Notizen</string>
<string name="on_this_day">An diesem Tag</string>
<string name="loading_account">Alles wird vorbereitet…</string>
@@ -45,4 +46,12 @@
<string name="options">Optionen</string>
<string name="tab_customizer_instructions">Durch langes Drücken und Ziehen können Sie die oberen Symbole neu anordnen.</string>
<string name="no_new_notifications">Keine neue Benachrichtigungen gefunden</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-de-rDE/strings_pref_feed.xml b/app/src/main/res/values-de-rDE/strings_pref_feed.xml
index 07844d89..8877de04 100644
--- a/app/src/main/res/values-de-rDE/strings_pref_feed.xml
+++ b/app/src/main/res/values-de-rDE/strings_pref_feed.xml
@@ -11,6 +11,8 @@
<string name="suggested_friends_desc">Zeige \"Leute die du vielleicht kennst\" im Feed</string>
<string name="suggested_groups">Empfohlene Gruppen</string>
<string name="suggested_groups_desc">Zeige \"Empfohlene Gruppen\" im Feed</string>
+ <string name="show_stories">Story\'s anzeigen</string>
+ <string name="show_stories_desc">Story\'s in den Feed anzeigen</string>
<string name="facebook_ads">Facebook Werbung</string>
<string name="facebook_ads_desc">Zeige native Facebook Werbung</string>
</resources>
diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml
index 6b96de27..200b923b 100644
--- a/app/src/main/res/values-es-rES/strings.xml
+++ b/app/src/main/res/values-es-rES/strings.xml
@@ -17,6 +17,7 @@
<string name="birthdays">Cumpleaños</string>
<string name="chat">Chat</string>
<string name="photos">Fotos</string>
+ <string name="marketplace">Marketplace</string>
<string name="notes">Notas</string>
<string name="on_this_day">En este día</string>
<string name="loading_account">Preparando todo…</string>
@@ -45,4 +46,14 @@
<string name="options">Opciones</string>
<string name="tab_customizer_instructions">Mantén pulsado y arrastra para reorganizar los iconos superiores.</string>
<string name="no_new_notifications">No se han encontrado Notificaciones</string>
+ <string name="today">Hoy</string>
+ <string name="yesterday">Ayer</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-es-rES/strings_pref_behaviour.xml b/app/src/main/res/values-es-rES/strings_pref_behaviour.xml
index dd64c6d1..925f058c 100644
--- a/app/src/main/res/values-es-rES/strings_pref_behaviour.xml
+++ b/app/src/main/res/values-es-rES/strings_pref_behaviour.xml
@@ -17,6 +17,8 @@
<string name="force_message_bottom_desc">Al cargar un hilo de mensaje, activa un desplazamiento hacia la parte inferior de la página en lugar de cargar la página tal como es.</string>
<string name="enable_pip">Activar PIP</string>
<string name="enable_pip_desc">Activar función de video en miniatura</string>
+ <string name="autoplay_settings">Configuración de jugadas automáticas</string>
+ <string name="autoplay_settings_desc">Abra configuración de juego de auto de Facebook. Tenga en cuenta que debe estar desactivada para que PIP trabajar.</string>
<string name="exit_confirmation">Confirmar salida</string>
<string name="exit_confirmation_desc">Muestra un diálogo de confirmación antes de salir de la app</string>
<string name="analytics">Analytics</string>
diff --git a/app/src/main/res/values-es-rES/strings_pref_feed.xml b/app/src/main/res/values-es-rES/strings_pref_feed.xml
index b5b3451d..2ebbdf87 100644
--- a/app/src/main/res/values-es-rES/strings_pref_feed.xml
+++ b/app/src/main/res/values-es-rES/strings_pref_feed.xml
@@ -11,6 +11,8 @@
<string name="suggested_friends_desc">Mostrar \"Gente que quizá conozcas\" en el feed</string>
<string name="suggested_groups">Grupos sugeridos</string>
<string name="suggested_groups_desc">Mostrar \"grupos sugeridos\" en el feed</string>
+ <string name="show_stories">Historias destacadas</string>
+ <string name="show_stories_desc">Mostrar historias en el feed</string>
<string name="facebook_ads">Anuncios de Facebook</string>
<string name="facebook_ads_desc">Mostrar anuncios nativos de Facebook</string>
</resources>
diff --git a/app/src/main/res/values-es/strings_pref_feed.xml b/app/src/main/res/values-es/strings_pref_feed.xml
deleted file mode 100644
index 646c87e2..00000000
--- a/app/src/main/res/values-es/strings_pref_feed.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--Generated by crowdin.com-->
-<resources>
- <string name="newsfeed_sort">Orden de las Publicaciones</string>
- <string name="newsfeed_sort_desc">Define el orden en que aparecen las publicaciones</string>
- <string name="aggressive_recents">Modo \"Más Recientes\" agresivo</string>
- <string name="aggressive_recents_desc">Filtra de manera adicional las publicaciones más antiguas de Facebook de las noticias recientes. Deshabilita esta opción si el feed se encuentra vacio.</string>
- <string name="composer">Escritor de Estado</string>
- <string name="composer_desc">Mostrar escritor de estado en el feed</string>
- <string name="suggested_friends">Sugerencias de Amigos</string>
- <string name="suggested_friends_desc">Mostrar \"Gente que quizá conozcas\" en el feed</string>
- <string name="suggested_groups">Grupos sugeridos</string>
- <string name="suggested_groups_desc">Mostrar \"grupos sugeridos\" en el feed</string>
- <string name="facebook_ads">Publicidad de Facebook</string>
- <string name="facebook_ads_desc">Mostrar Publicidad Nativa de Facebook</string>
-</resources>
diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml
index 16b14ac2..b0e179e9 100644
--- a/app/src/main/res/values-fr-rFR/strings.xml
+++ b/app/src/main/res/values-fr-rFR/strings.xml
@@ -17,6 +17,7 @@
<string name="birthdays">Anniversaires</string>
<string name="chat">Conversations</string>
<string name="photos">Photos</string>
+ <string name="marketplace">Marketplace</string>
<string name="notes">Notes</string>
<string name="on_this_day">Aujourd\'hui</string>
<string name="loading_account">Tout se prépare…</string>
@@ -45,4 +46,15 @@
<string name="options">Options</string>
<string name="tab_customizer_instructions">Appuyez longuement et faites glisser pour réorganiser les icônes du haut.</string>
<string name="no_new_notifications">Pas de nouvelles notifications trouvées</string>
+ <string name="today">Aujourd\'hui</string>
+ <string name="yesterday">Hier</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
+ <string name="time_template">%1s à %2s</string>
</resources>
diff --git a/app/src/main/res/values-fr-rFR/strings_pref_behaviour.xml b/app/src/main/res/values-fr-rFR/strings_pref_behaviour.xml
index 9c819308..7dec9c46 100644
--- a/app/src/main/res/values-fr-rFR/strings_pref_behaviour.xml
+++ b/app/src/main/res/values-fr-rFR/strings_pref_behaviour.xml
@@ -17,6 +17,8 @@
<string name="force_message_bottom_desc">Lors du chargement d’un fil de message, déclencher un défilement vers le bas de la page au lieu de charger la page telle quelle.</string>
<string name="enable_pip">Activer le PIP</string>
<string name="enable_pip_desc">Activer les vidéos Picture In Picture</string>
+ <string name="autoplay_settings">Paramètres de lecture automatique</string>
+ <string name="autoplay_settings_desc">Ouvrir les paramètres de lecture automatique de Facebook. Notez qu\'il doit être désactivé pour que PIP fonctionne.</string>
<string name="exit_confirmation">Confirmation de la sortie</string>
<string name="exit_confirmation_desc">Afficher la boîte de dialogue de confirmation avant de quitter l’application</string>
<string name="analytics">Analytics</string>
diff --git a/app/src/main/res/values-fr-rFR/strings_pref_feed.xml b/app/src/main/res/values-fr-rFR/strings_pref_feed.xml
index 581d869a..1de2ab0c 100644
--- a/app/src/main/res/values-fr-rFR/strings_pref_feed.xml
+++ b/app/src/main/res/values-fr-rFR/strings_pref_feed.xml
@@ -4,13 +4,15 @@
<string name="newsfeed_sort">Ordre du fil d\'actualité</string>
<string name="newsfeed_sort_desc">Définit l’ordre dans lequel les messages sont affichés</string>
<string name="aggressive_recents">Récents agressifs</string>
- <string name="aggressive_recents_desc">Filtrer les vieilles publications additionnelles du fil d\'actualité les plus récentes de Facebook. Désactivez cette option si votre fil d\'actualités est vide.</string>
+ <string name="aggressive_recents_desc">Éliminer les anciennes publications additionnelles du fil d\'actualité récentes de Facebook. Désactivez cette option si votre fil d\'actualités est vide.</string>
<string name="composer">Compositeur de statut</string>
<string name="composer_desc">Montrer le compositeur de statut dans le fil d\'actualité</string>
<string name="suggested_friends">Amis suggérés</string>
<string name="suggested_friends_desc">Afficher les «Personnes que vous pouvez connaître» dans le fil d\'actualité</string>
<string name="suggested_groups">Groupes Suggérés</string>
<string name="suggested_groups_desc">Afficher les «Groupes Suggérés» dans le fil d\'actualité</string>
+ <string name="show_stories">Montrer les Top Stories</string>
+ <string name="show_stories_desc">Montrer les stories dans le fil d\'actualité</string>
<string name="facebook_ads">Publicités Facebook</string>
<string name="facebook_ads_desc">Afficher les publicités Facebook</string>
</resources>
diff --git a/app/src/main/res/values-gl-rES/strings.xml b/app/src/main/res/values-gl-rES/strings.xml
index 9d9c8e50..48547a6c 100644
--- a/app/src/main/res/values-gl-rES/strings.xml
+++ b/app/src/main/res/values-gl-rES/strings.xml
@@ -48,4 +48,12 @@
<string name="options">Opcións</string>
<string name="tab_customizer_instructions">Toque longo e arrastra para reorganizar as iconas superiores.</string>
<string name="no_new_notifications">Non se atopou ningunha nova notificación</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml
index 1978cacf..6b62f285 100644
--- a/app/src/main/res/values-hu-rHU/strings.xml
+++ b/app/src/main/res/values-hu-rHU/strings.xml
@@ -17,6 +17,7 @@
<string name="birthdays">Születésnapok</string>
<string name="chat">Chat</string>
<string name="photos">Fényképek</string>
+ <string name="marketplace">Piactér</string>
<string name="notes">Jegyzetek</string>
<string name="on_this_day">Ezen a napon</string>
<string name="loading_account">Előkészítés…</string>
@@ -45,4 +46,12 @@
<string name="options">Beállítások</string>
<string name="tab_customizer_instructions">Tartsd nyomva és húzd a felső ikonokat az átrendezéshez.</string>
<string name="no_new_notifications">Nem találhatók új értesítések</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-hu-rHU/strings_pref_feed.xml b/app/src/main/res/values-hu-rHU/strings_pref_feed.xml
index fa85b06e..a784c376 100644
--- a/app/src/main/res/values-hu-rHU/strings_pref_feed.xml
+++ b/app/src/main/res/values-hu-rHU/strings_pref_feed.xml
@@ -11,6 +11,8 @@
<string name="suggested_friends_desc">\"Emberek, akiket ismerhetsz\" megjelenítése a hírcsatornában</string>
<string name="suggested_groups">Javasolt csoportok</string>
<string name="suggested_groups_desc">\"Javasolt csoportok\" megjelenítése a hírcsatornában</string>
+ <string name="show_stories">Történetek megjelenítése</string>
+ <string name="show_stories_desc">Történetek megjelenítése a hírfolyamban</string>
<string name="facebook_ads">Facebook hirdetések</string>
<string name="facebook_ads_desc">Natív Facebook-hirdetések megjelenítése</string>
</resources>
diff --git a/app/src/main/res/values-in-rID/strings.xml b/app/src/main/res/values-in-rID/strings.xml
index fd6c3abe..73c6c61d 100644
--- a/app/src/main/res/values-in-rID/strings.xml
+++ b/app/src/main/res/values-in-rID/strings.xml
@@ -45,4 +45,12 @@
<string name="preview">Pratinjau</string>
<string name="options">Pilihan</string>
<string name="tab_customizer_instructions">Tekan lama dan tarik untuk mengatur ulang ikon atas.</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml
index 5a5acbd6..be8c04f4 100644
--- a/app/src/main/res/values-it-rIT/strings.xml
+++ b/app/src/main/res/values-it-rIT/strings.xml
@@ -46,4 +46,12 @@
<string name="options">Opzioni</string>
<string name="tab_customizer_instructions">Per riordinare un\'icona tienila premuta e trascinala.</string>
<string name="no_new_notifications">Nessuna nuova notifica trovata</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-it/strings_pref_feed.xml b/app/src/main/res/values-it/strings_pref_feed.xml
deleted file mode 100644
index e04dc077..00000000
--- a/app/src/main/res/values-it/strings_pref_feed.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--Generated by crowdin.com-->
-<resources>
- <string name="newsfeed_sort">Ordine della Sezione Notizie</string>
- <string name="newsfeed_sort_desc">Definisce l\'ordine in cui sono mostrati i post</string>
- <string name="aggressive_recents">Recenti Aggressivi</string>
- <string name="aggressive_recents_desc">Filtra ulteriori post vecchi dalla sezione originale più recenti di Facebook. Disabilita se il tuo feed è vuoto.</string>
- <string name="composer">Compositore di Stato</string>
- <string name="composer_desc">Mostra la casella per comporre uno stato nelle Notizie</string>
- <string name="suggested_friends">Amici Suggeriti</string>
- <string name="suggested_friends_desc">Mostra \"Persone Che Potresti Conoscere\" nel feed</string>
- <string name="suggested_groups">Gruppi Suggeriti</string>
- <string name="suggested_groups_desc">Mostra \"Gruppi Suggeriti\" nel feed</string>
- <string name="facebook_ads">Pubblicità Facebook</string>
- <string name="facebook_ads_desc">Mostra le pubblicità native di Facebook</string>
-</resources>
diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml
index df91e3e0..ef490091 100644
--- a/app/src/main/res/values-ko-rKR/strings.xml
+++ b/app/src/main/res/values-ko-rKR/strings.xml
@@ -42,4 +42,12 @@
<string name="file_chooser_not_found">파일 선택기를 찾을 수 없습니다.</string>
<string name="top_bar">상단 바</string>
<string name="bottom_bar">하단 바</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-nl-rNL/strings.xml b/app/src/main/res/values-nl-rNL/strings.xml
index 5a0373f7..0c506137 100644
--- a/app/src/main/res/values-nl-rNL/strings.xml
+++ b/app/src/main/res/values-nl-rNL/strings.xml
@@ -46,4 +46,12 @@
<string name="options">Opties</string>
<string name="tab_customizer_instructions">Klik en houd vast om de iconen in de gewenste volgorde te slepen.</string>
<string name="no_new_notifications">Geen nieuwe notificaties</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-no-rNO/strings.xml b/app/src/main/res/values-no-rNO/strings.xml
index 08aab533..770ee6d8 100644
--- a/app/src/main/res/values-no-rNO/strings.xml
+++ b/app/src/main/res/values-no-rNO/strings.xml
@@ -44,4 +44,12 @@
<string name="preview">Forhåndsvisning</string>
<string name="options">Alternativer</string>
<string name="tab_customizer_instructions">Langt trykk og dra for å endre topp ikonene.</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml
index 35b3c991..8d8dca94 100644
--- a/app/src/main/res/values-pl-rPL/strings.xml
+++ b/app/src/main/res/values-pl-rPL/strings.xml
@@ -45,4 +45,12 @@
<string name="options">Opcje</string>
<string name="tab_customizer_instructions">Długie naciśnięcie i przeciągnięcie, aby zmienić kolejność ikon.</string>
<string name="no_new_notifications">Brak nowych powiadomień</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 9525dec8..cca038df 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -17,6 +17,7 @@
<string name="birthdays">Aniversários</string>
<string name="chat">Amigos online</string>
<string name="photos">Fotos</string>
+ <string name="marketplace">Marketplace</string>
<string name="notes">Notas</string>
<string name="on_this_day">Neste Dia</string>
<string name="loading_account">Preparando tudo…</string>
@@ -46,4 +47,15 @@
<string name="options">Opções</string>
<string name="tab_customizer_instructions">Mantenha pressionado e arraste para reorganizar os ícones superiores.</string>
<string name="no_new_notifications">Nenhuma nova notificação encontrada</string>
+ <string name="today">Hoje</string>
+ <string name="yesterday">Ontem</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
+ <string name="time_template">%1s às %2s</string>
</resources>
diff --git a/app/src/main/res/values-pt-rBR/strings_pref_behaviour.xml b/app/src/main/res/values-pt-rBR/strings_pref_behaviour.xml
index db06b1e8..7355c3d5 100644
--- a/app/src/main/res/values-pt-rBR/strings_pref_behaviour.xml
+++ b/app/src/main/res/values-pt-rBR/strings_pref_behaviour.xml
@@ -17,6 +17,8 @@
<string name="force_message_bottom_desc">Ao carregar um tópico de mensagem, aciona uma rolagem para a parte inferior da página em vez de carregar a página como está.</string>
<string name="enable_pip">Habilitar o PIP</string>
<string name="enable_pip_desc">Habilita o Picture in Picture (janelas flutuantes de vídeos)</string>
+ <string name="autoplay_settings">Configurações de reprodução automática</string>
+ <string name="autoplay_settings_desc">Abra as configurações de reprodução automática do Facebook. Observe que ele deve ser desativado para que o PIP funcione.</string>
<string name="exit_confirmation">Confirmação de Saída</string>
<string name="exit_confirmation_desc">Mostrar caixa de diálogo de confirmação antes de sair do aplicativo</string>
<string name="analytics">Telemetria</string>
diff --git a/app/src/main/res/values-pt-rBR/strings_pref_feed.xml b/app/src/main/res/values-pt-rBR/strings_pref_feed.xml
index e03b132e..1a37345f 100644
--- a/app/src/main/res/values-pt-rBR/strings_pref_feed.xml
+++ b/app/src/main/res/values-pt-rBR/strings_pref_feed.xml
@@ -11,6 +11,8 @@
<string name="suggested_friends_desc">Mostra \"Pessoas Que Talvez Você Conheça\" no Feed</string>
<string name="suggested_groups">Grupos Sugeridos</string>
<string name="suggested_groups_desc">Mostra \"Grupos Sugeridos\" no Feed</string>
+ <string name="show_stories">Mostrar Histórias</string>
+ <string name="show_stories_desc">Mostrar histórias no feed</string>
<string name="facebook_ads">Anúncios do Facebook</string>
<string name="facebook_ads_desc">Mostrar anúncios nativos do Facebook</string>
</resources>
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index 471abe90..0bbdc5ad 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -45,4 +45,12 @@
<string name="options">Opções</string>
<string name="tab_customizer_instructions">Toque longo e arraste para dispor os ícones superiores.</string>
<string name="no_new_notifications">Sem notificações novas</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-ro-rRO/strings.xml b/app/src/main/res/values-ro-rRO/strings.xml
index a1f53622..1fcfce92 100644
--- a/app/src/main/res/values-ro-rRO/strings.xml
+++ b/app/src/main/res/values-ro-rRO/strings.xml
@@ -46,4 +46,12 @@
<string name="options">Opțiuni</string>
<string name="tab_customizer_instructions">Apasă lung și trage să rearanjezi.</string>
<string name="no_new_notifications">Nu s-au găsit notificări</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml
index 3ac25564..3a0c30af 100644
--- a/app/src/main/res/values-ru-rRU/strings.xml
+++ b/app/src/main/res/values-ru-rRU/strings.xml
@@ -17,6 +17,7 @@
<string name="birthdays">Дни рождения</string>
<string name="chat">Написать</string>
<string name="photos">Фотографии</string>
+ <string name="marketplace">Marketplace</string>
<string name="notes">Заметки</string>
<string name="on_this_day">В этот день</string>
<string name="loading_account">Почти готово…</string>
@@ -45,4 +46,14 @@
<string name="options">Опции</string>
<string name="tab_customizer_instructions">Долго нажмите и перетащите чтобы переставить иконки</string>
<string name="no_new_notifications">Новые уведомления отсутствуют</string>
+ <string name="today">Сегодня</string>
+ <string name="yesterday">Вчера</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-ru-rRU/strings_pref_feed.xml b/app/src/main/res/values-ru-rRU/strings_pref_feed.xml
index 61718079..d48a56f1 100644
--- a/app/src/main/res/values-ru-rRU/strings_pref_feed.xml
+++ b/app/src/main/res/values-ru-rRU/strings_pref_feed.xml
@@ -11,6 +11,8 @@
<string name="suggested_friends_desc">Смотреть «Люди которых вы можете знать» в канале</string>
<string name="suggested_groups">Предлагаемые группы</string>
<string name="suggested_groups_desc">Смотреть «Предложения групп» в канале</string>
+ <string name="show_stories">Показывать Истории</string>
+ <string name="show_stories_desc">Показывать Истории в ленте</string>
<string name="facebook_ads">- Реклама в Facebook</string>
<string name="facebook_ads_desc">Показать родной Facebook объявления</string>
</resources>
diff --git a/app/src/main/res/values-sr-rSP/strings.xml b/app/src/main/res/values-sr-rSP/strings.xml
index f526edcf..9cb3ef9c 100644
--- a/app/src/main/res/values-sr-rSP/strings.xml
+++ b/app/src/main/res/values-sr-rSP/strings.xml
@@ -45,4 +45,12 @@
<string name="options">Опције</string>
<string name="tab_customizer_instructions">Задржите и превуците да би прерасподелили горње иконице.</string>
<string name="no_new_notifications">Нема нових обавештења</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml
index aeabdbb4..456bcae1 100644
--- a/app/src/main/res/values-sv-rSE/strings.xml
+++ b/app/src/main/res/values-sv-rSE/strings.xml
@@ -46,4 +46,12 @@
<string name="options">Inställningar</string>
<string name="tab_customizer_instructions">Tryck och håll kvar för att arrangera om topp-ikonerna.</string>
<string name="no_new_notifications">Inga nya notifikationer hittades</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-th-rTH/strings.xml b/app/src/main/res/values-th-rTH/strings.xml
index 5c1e63ec..4f2c4734 100644
--- a/app/src/main/res/values-th-rTH/strings.xml
+++ b/app/src/main/res/values-th-rTH/strings.xml
@@ -44,4 +44,12 @@
<string name="preview">แสดงตัวอย่าง</string>
<string name="options">ตัวเลือก</string>
<string name="tab_customizer_instructions">กดค้างและลากเพื่อจัดเรียงไอคอนด้านบน</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-tl-rPH/strings.xml b/app/src/main/res/values-tl-rPH/strings.xml
index e9139373..6880e40e 100644
--- a/app/src/main/res/values-tl-rPH/strings.xml
+++ b/app/src/main/res/values-tl-rPH/strings.xml
@@ -45,4 +45,12 @@
<string name="preview">Pribyu</string>
<string name="options">Ang mga opsyon</string>
<string name="tab_customizer_instructions">Pindutin ng matagal at hilahin para mabago ang ayos ng pangunahing imahe.</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml
index 828a2abb..f2dc54b2 100644
--- a/app/src/main/res/values-tr-rTR/strings.xml
+++ b/app/src/main/res/values-tr-rTR/strings.xml
@@ -17,6 +17,7 @@
<string name="birthdays">Doğum Günleri</string>
<string name="chat">Sohbet</string>
<string name="photos">Fotoğraflar</string>
+ <string name="marketplace">Pazar yeri</string>
<string name="notes">Notlar</string>
<string name="on_this_day">Bu günde</string>
<string name="loading_account">Her şey hazır alınıyor…</string>
@@ -45,4 +46,14 @@
<string name="options">Seçenekler</string>
<string name="tab_customizer_instructions">Üstteki simgeleri yeniden düzenlemek için uzun basın ve sonra sürükleyin.</string>
<string name="no_new_notifications">Yeni bildirim bulunmadı</string>
+ <string name="today">Bugün</string>
+ <string name="yesterday">Dün</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-tr-rTR/strings_pref_behaviour.xml b/app/src/main/res/values-tr-rTR/strings_pref_behaviour.xml
index 9c60e461..58f3ca43 100644
--- a/app/src/main/res/values-tr-rTR/strings_pref_behaviour.xml
+++ b/app/src/main/res/values-tr-rTR/strings_pref_behaviour.xml
@@ -21,6 +21,7 @@
<string name="force_message_bottom_desc">Birileti dizisi yüklerken, sayfayı olduğu gibi yüklemek yerine, sayfanın altına kaydırma yapın.</string>
<string name="enable_pip">PIP\'i etkinleştir</string>
<string name="enable_pip_desc">PIP (Picture in Picture) videolarını etkinleştir</string>
+ <string name="autoplay_settings">Otomatik oynatma ayarları</string>
<string name="exit_confirmation">Çıkış Onayı</string>
<string name="exit_confirmation_desc">Uygulamadan çıkmadanönce onay iletişim kutusunu göster</string>
<string name="analytics">Analiz</string>
diff --git a/app/src/main/res/values-tr-rTR/strings_pref_feed.xml b/app/src/main/res/values-tr-rTR/strings_pref_feed.xml
index 30963338..e70e96cf 100644
--- a/app/src/main/res/values-tr-rTR/strings_pref_feed.xml
+++ b/app/src/main/res/values-tr-rTR/strings_pref_feed.xml
@@ -11,6 +11,8 @@
<string name="suggested_friends_desc">Özet akışında \"Tanıdığınız İnsanları\" gösterin</string>
<string name="suggested_groups">Önerilen gruplar</string>
<string name="suggested_groups_desc">Özet akışında \"önerilen grup\" ları göster</string>
+ <string name="show_stories">Hikayeleri göster</string>
+ <string name="show_stories_desc">Akışda ki hikayeleri göster</string>
<string name="facebook_ads">Facebook reklamları</string>
<string name="facebook_ads_desc">Yerli Facebook reklamlarını göster</string>
</resources>
diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml
index 7d051a3f..1abb84d5 100644
--- a/app/src/main/res/values-uk-rUA/strings.xml
+++ b/app/src/main/res/values-uk-rUA/strings.xml
@@ -17,6 +17,7 @@
<string name="birthdays">Дні народження</string>
<string name="chat">Чат</string>
<string name="photos">Фотографії</string>
+ <string name="marketplace">Магазин</string>
<string name="notes">Замітки</string>
<string name="on_this_day">Цього дня</string>
<string name="loading_account">Отримання всього готове…</string>
@@ -45,4 +46,12 @@
<string name="options">Опції</string>
<string name="tab_customizer_instructions">Довге натискання та перетягніть, щоб переставити верхній значок.</string>
<string name="no_new_notifications">Нових повідомлень не знайдено</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-uk-rUA/strings_pref_feed.xml b/app/src/main/res/values-uk-rUA/strings_pref_feed.xml
index 1b4176e0..29ecc9d6 100644
--- a/app/src/main/res/values-uk-rUA/strings_pref_feed.xml
+++ b/app/src/main/res/values-uk-rUA/strings_pref_feed.xml
@@ -11,6 +11,8 @@
<string name="suggested_friends_desc">Показати \"Люди, яких ви можете знати\" у новинній стрічці</string>
<string name="suggested_groups">Пропоновані групи</string>
<string name="suggested_groups_desc">Показати \"Пропоновані групи\" у новинній стрічці</string>
+ <string name="show_stories">Показати Історії</string>
+ <string name="show_stories_desc">Показувати історії у стрічці</string>
<string name="facebook_ads">Реклама у Facebook</string>
<string name="facebook_ads_desc">Показувати вбудовану рекламу Facebook</string>
</resources>
diff --git a/app/src/main/res/values-vi-rVN/strings.xml b/app/src/main/res/values-vi-rVN/strings.xml
index f65193bc..9b7c39e6 100644
--- a/app/src/main/res/values-vi-rVN/strings.xml
+++ b/app/src/main/res/values-vi-rVN/strings.xml
@@ -46,4 +46,12 @@
<string name="options">Tuỳ chọn</string>
<string name="tab_customizer_instructions">Bấm giữ và kéo để sắp xếp biểu tượng trên cùng.</string>
<string name="no_new_notifications">Không có thông báo mới</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 76105ca1..bf5ced6a 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -40,4 +40,12 @@
<string name="file_chooser_not_found">未找到文件选择程序</string>
<string name="top_bar">顶栏</string>
<string name="bottom_bar">底栏</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 5ee8d7bd..3d22540b 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -45,4 +45,12 @@
<string name="options">選項</string>
<string name="tab_customizer_instructions">長按及拖曳頂部圖標可重新排列位置</string>
<string name="no_new_notifications">沒有新通知。</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
</resources>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 713bd1b4..847e74cb 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -9,4 +9,6 @@
<dimen name="tab_bar_height">50dp</dimen>
<dimen name="intro_bar_height">64dp</dimen>
<dimen name="badge_icon_size">20dp</dimen>
+
+ <dimen name="toolbar_icon_size">24dp</dimen>
</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 10481b50..5eb1c9e7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -64,4 +64,15 @@
<!--Biometrics-->
<string name="biometrics_prompt_title">Authenticate Frost</string>
+ <string name="today">Today</string>
+ <string name="yesterday">Yesterday</string>
+ <!--
+ Template used to display human readable string;
+ For instance:
+ Today at 1:23 PM
+ Mar 13 at 9:00 AM
+
+ The first element is the day, and the second element is the time
+ -->
+ <string name="time_template">%1s at %2s</string>
</resources>
diff --git a/app/src/main/res/xml/frost_changelog.xml b/app/src/main/res/xml/frost_changelog.xml
index 1895e46f..560b1111 100644
--- a/app/src/main/res/xml/frost_changelog.xml
+++ b/app/src/main/res/xml/frost_changelog.xml
@@ -6,12 +6,22 @@
<item text="" />
-->
+ <version title="v2.3.0" />
+ <item text="Converted internals of Facebook data storage; auto migration will only work from 2.2.x to 2.3.x" />
+ <item text="Added notification widget" />
+ <item text="" />
+ <item text="" />
+ <item text="" />
+ <item text="" />
+ <item text="" />
+ <item text="" />
+ <item text="" />
+
<version title="v2.2.4" />
<item text="Show top bar to allow sharing posts" />
<item text="Fix unmuting videos when autoplay is enabled" />
<item text="Add shortcut to toggle autoplay in settings > behaviour" />
<item text="Update theme" />
- <item text="" />
<version title="v2.2.3" />
<item text="Add ability to hide stories" />
diff --git a/app/src/main/res/xml/notification_widget_info.xml b/app/src/main/res/xml/notification_widget_info.xml
new file mode 100644
index 00000000..c14bbfb2
--- /dev/null
+++ b/app/src/main/res/xml/notification_widget_info.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+For sizing see:
+https://developer.android.com/guide/practices/ui_guidelines/widget_design.html#anatomy_determining_size
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+ android:initialKeyguardLayout="@layout/widget_notifications"
+ android:initialLayout="@layout/widget_notifications"
+ android:minWidth="180dp"
+ android:minHeight="250dp"
+ android:previewImage="@drawable/notification_widget_preview" />
diff --git a/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json
new file mode 100644
index 00000000..a0cc2c2a
--- /dev/null
+++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json
@@ -0,0 +1,195 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 1,
+ "identityHash": "fe8f5b6c27f48d7e0733ee6819f06f40",
+ "entities": [
+ {
+ "tableName": "cookies",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`cookie_id` INTEGER NOT NULL, `name` TEXT, `cookie` TEXT, PRIMARY KEY(`cookie_id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "cookie_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "cookie",
+ "columnName": "cookie",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "cookie_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "notifications",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`notif_id` INTEGER NOT NULL, `userId` INTEGER NOT NULL, `href` TEXT NOT NULL, `title` TEXT, `text` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `profileUrl` TEXT, `type` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`notif_id`, `userId`), FOREIGN KEY(`userId`) REFERENCES `cookies`(`cookie_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "notif_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userId",
+ "columnName": "userId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "href",
+ "columnName": "href",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "timestamp",
+ "columnName": "timestamp",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "profileUrl",
+ "columnName": "profileUrl",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unread",
+ "columnName": "unread",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "notif_id",
+ "userId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_notifications_notif_id",
+ "unique": false,
+ "columnNames": [
+ "notif_id"
+ ],
+ "createSql": "CREATE INDEX `index_notifications_notif_id` ON `${TABLE_NAME}` (`notif_id`)"
+ },
+ {
+ "name": "index_notifications_userId",
+ "unique": false,
+ "columnNames": [
+ "userId"
+ ],
+ "createSql": "CREATE INDEX `index_notifications_userId` ON `${TABLE_NAME}` (`userId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "cookies",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "userId"
+ ],
+ "referencedColumns": [
+ "cookie_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "frost_cache",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, `contents` TEXT NOT NULL, PRIMARY KEY(`id`, `type`), FOREIGN KEY(`id`) REFERENCES `cookies`(`cookie_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastUpdated",
+ "columnName": "lastUpdated",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "contents",
+ "columnName": "contents",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id",
+ "type"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": [
+ {
+ "table": "cookies",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "id"
+ ],
+ "referencedColumns": [
+ "cookie_id"
+ ]
+ }
+ ]
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"fe8f5b6c27f48d7e0733ee6819f06f40\")"
+ ]
+ }
+} \ No newline at end of file
diff --git a/app/src/schemas/com.pitchedapps.frost.db.FrostPublicDatabase/1.json b/app/src/schemas/com.pitchedapps.frost.db.FrostPublicDatabase/1.json
new file mode 100644
index 00000000..4a523c62
--- /dev/null
+++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPublicDatabase/1.json
@@ -0,0 +1,40 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 1,
+ "identityHash": "ee4d2fe4052ad3a1892be17681816c2c",
+ "entities": [
+ {
+ "tableName": "frost_generic",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `contents` TEXT NOT NULL, PRIMARY KEY(`type`))",
+ "fields": [
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "contents",
+ "columnName": "contents",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "type"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"ee4d2fe4052ad3a1892be17681816c2c\")"
+ ]
+ }
+} \ No newline at end of file
diff --git a/app/src/test/kotlin/com/pitchedapps/frost/debugger/OfflineWebsiteTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/debugger/OfflineWebsiteTest.kt
index a2bafba9..0ad60126 100644
--- a/app/src/test/kotlin/com/pitchedapps/frost/debugger/OfflineWebsiteTest.kt
+++ b/app/src/test/kotlin/com/pitchedapps/frost/debugger/OfflineWebsiteTest.kt
@@ -27,9 +27,9 @@ import org.junit.Assume.assumeTrue
import java.io.File
import java.util.zip.ZipFile
import kotlin.test.AfterTest
-import kotlin.test.Test
import kotlin.test.BeforeTest
import kotlin.test.Ignore
+import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
diff --git a/app/src/test/kotlin/com/pitchedapps/frost/facebook/parsers/FbParseTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/facebook/parsers/FbParseTest.kt
index 075f045e..11e2502b 100644
--- a/app/src/test/kotlin/com/pitchedapps/frost/facebook/parsers/FbParseTest.kt
+++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/parsers/FbParseTest.kt
@@ -21,7 +21,9 @@ import com.pitchedapps.frost.internal.assertComponentsNotEmpty
import com.pitchedapps.frost.internal.assertDescending
import com.pitchedapps.frost.internal.authDependent
import org.junit.BeforeClass
+import org.junit.Ignore
import org.junit.Test
+import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import kotlin.test.fail
@@ -39,13 +41,14 @@ class FbParseTest {
}
}
- private inline fun <reified T : Any> FrostParser<T>.test(action: T.() -> Unit = {}) =
+ private inline fun <reified T : ParseData> FrostParser<T>.test(action: T.() -> Unit = {}) =
parse(COOKIE).test(url, action)
- private inline fun <reified T : Any> ParseResponse<T>?.test(url: String, action: T.() -> Unit = {}) {
+ private inline fun <reified T : ParseData> ParseResponse<T>?.test(url: String, action: T.() -> Unit = {}) {
val response = this
?: fail("${T::class.simpleName} parser returned null for $url")
println(response)
+ assertFalse(response.data.isEmpty, "${T::class.simpleName} parser returned empty data for $url")
response.data.action()
}
@@ -62,6 +65,7 @@ class FbParseTest {
@Test
fun messageUser() = MessageParser.queryUser(COOKIE, "allan").test("allan query")
+ @Ignore("No longer works as search results don't appear in html")
@Test
fun search() = SearchParser.test()
diff --git a/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt b/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt
index b8d9635a..41473e86 100644
--- a/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt
+++ b/app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt
@@ -59,7 +59,7 @@ val AUTH: RequestAuth by lazy {
}
}
-val VALID_COOKIE: Boolean by lazy {
+private val VALID_COOKIE: Boolean by lazy {
val data = testJsoup(FbItem.SETTINGS.url)
data.title() == "Settings"
}
@@ -68,7 +68,8 @@ fun testJsoup(url: String) = frostJsoup(COOKIE, url)
fun authDependent() {
println("Auth Dependent")
- Assume.assumeTrue(COOKIE.isNotEmpty() && VALID_COOKIE)
+ Assume.assumeTrue("Cookie cannot be empty", COOKIE.isNotEmpty())
+ Assume.assumeTrue("Cookie is not valid", VALID_COOKIE)
}
/**
diff --git a/app/src/web/.gitignore b/app/src/web/.gitignore
index 76a547ef..aae31b8a 100644
--- a/app/src/web/.gitignore
+++ b/app/src/web/.gitignore
@@ -1,6 +1,8 @@
node_modules/
.sass-cache/
-package-lock.json
+
+*.js
+*.css
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
diff --git a/app/src/web/.idea/watcherTasks.xml b/app/src/web/.idea/watcherTasks.xml
index 4caea6ed..2b679ae8 100644
--- a/app/src/web/.idea/watcherTasks.xml
+++ b/app/src/web/.idea/watcherTasks.xml
@@ -2,14 +2,14 @@
<project version="4">
<component name="ProjectTasksOptions">
<TaskOptions isEnabled="true">
- <option name="arguments" value="--no-source-map --update $FileName$:$FileNameWithoutExtension$.css" />
+ <option name="arguments" value="--no-source-map --style compressed --update $FileName$:../../assets/css/$FileDirName$/$FileNameWithoutExtension$.css" />
<option name="checkSyntaxErrors" value="true" />
<option name="description" />
<option name="exitCodeBehavior" value="ERROR" />
<option name="fileExtension" value="scss" />
<option name="immediateSync" value="true" />
<option name="name" value="SCSS" />
- <option name="output" value="$FileNameWithoutExtension$.css" />
+ <option name="output" value="../../assets/css/$FileDirName$/$FileNameWithoutExtension$.css" />
<option name="outputFilters">
<array />
</option>
diff --git a/app/src/web/assets/css/core/core.css b/app/src/web/assets/css/core/core.css
deleted file mode 100644
index d9a9dfd4..00000000
--- a/app/src/web/assets/css/core/core.css
+++ /dev/null
@@ -1,307 +0,0 @@
-body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5,
-._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk,
-.touched *, ._1_yj, ._1_yl, ._4pj9, ._2bdc, ._3qdh ._3qdn ._3qdk, ._3qdk ._48_q,
-._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, .cq, ._usr,
-._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5,
-._1lf4, ._1hiz, ._xod, ._5ag5, ._zmk, ._3t_h, ._5lm6, ._3clv, ._3zlc, ._36rd,
-._31zk, ._31zl, ._3xsa, ._3xs9, ._2-4s, ._2fzz ul, ._3z10, ._4mo, ._2om6,
-._43mh, .touch .btn, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35,
-._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu,
-._18qg, ._1_ac, ._529p, ._4dwt ._1vh3, ._4a5f, ._23_t, ._2rzc, ._23_s, ._2rzd,
-._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._36e0, ._4-dy,
-._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, .mCount, ._27vc, ._24e1, ._2rbw, ._3iyw ._3mzw,
-textarea:not([style*="color: rgb"]), ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782,
-._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._6--d, ._77p7, ._7h_g,
-._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._38d-, ._3n8y, ._38dt, ._3oyg, ._21dc,
-._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._6xqt, ._7cui,
-._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._7cdj,
-._2new, .appCenterCategorySelectorButton, ._1ksq, ._1kt6, ._6ber, ._mxb, ._3oyd, ._3gir, ._3gis,
-div.sharerSelector, .footer, ._4pv_, ._1dbp, ._3kad, ._20zc, ._2i5v, ._2i5w,
-a, ._5fpq, ._4gux, ._3bg5 ._52x1, ._3bg5 ._52x2, ._6dsj ._3gin, ._hdn._hdn,
-.mentions-input:not([style*="color: rgb"]), .mentions-placeholder:not([style*="color: rgb"]),
-.largeStatusBox .placeHolder, .fcw, ._2rgt, ._67i4 ._5hu6 ._59tt,
-._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4,
-._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs,
-h1, h2, h3, h4, h5, h6 {
- color: #d7b0d7 !important;
-}
-
-strong > a, ._15ks ._2q8z._2q8z, ._1e3e, .blueName, ._5kqs ._55sr {
- color: #980008 !important;
-}
-
-._42nf ._42ng {
- color: transparent !important;
-}
-
-p > a, .msg span > a {
- color: #9266d5 !important;
-}
-
-#viewport {
- background: #451515 !important;
-}
-
-body, :root, #root, #header, #MComposer, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4,
-._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._3qdh, ._8ca, ._3h8i,
-._6-l ._2us7, ._6-l ._6-p:not([style*="background-image:"]), ._333v, div.sharerSelector, ._529j, ._305j, ._1pph, ._3t_l, ._4pvz,
-._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._24e1, ._-j7,
-._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz,
-._38do, .bo, .cq, ._234-, ._a-5, ._2zh4, ._15ks, ._3oyc, ._36dc, ._3iyw ._3iyx, ._6bes, ._55wo, ._4-dy,
-.tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0,
-.al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._2bdb, ._3ci9,
-._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._6be7,
-._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._5xp2, div.fullwidthMore,
-._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._voz, ._vos,
-._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5_ee, ._3clk,
-._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5,
-._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, ._3tl8, ._65wz, ._4edl,
-.acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._1glm,
-._ue6, ._hdn._hdn, ._6vzw, ._77xj, ._38nq, ._9_7, ._51li,
-._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3bmj, ._5zmb, ._2x2s, ._3kac, ._3kad,
-._3f50, .mentions-placeholder, .mentions, .mentions-measurer, .acg, ._59tu,
-._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._1dbp, ._5zma, ._6beq, ._vi6,
-._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy,
-._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x {
- background: rgba(255, 0, 255, 0.02) !important;
-}
-
-._31nf, ._2v9s, ._d4i, article._55wo, ._10c_, ._2jl2, ._6150, ._50mi, ._4-dw, ._4_2z, ._5m_s, ._13fn {
- background: #239645 !important;
-}
-
-.aclb {
- background: #ff4682 !important;
-}
-
-._cv_, ._2sq8 {
- background-color: rgba(255, 0, 255, 0.02) !important;
-}
-
-#page, ._8l7, ._-j8, ._-j9, ._6o5v, ._uwx, .touch ._uwx.mentions-input {
- background: transparent !important;
-}
-
-.jewel, .flyout, ._52z5, ._13e_, ._5-lw, ._5c0e, .jx-result, ._336p, .mentions-suggest-item, ._2suk,
-.mentions-suggest, ._1xoz, ._1xow {
- background: #451515 !important;
-}
-
-._403n, ._14v5 ._14v8, ._1-kc {
- background: #c74646 !important;
-}
-
-button:not([style*=image]):not(.privacyButtons), button::before, .touch ._56bt, ._56be::before, .btnS, .touch::before,
-._590n, ._4g8h, ._2cpp, ._58a0.touched:after,
-.timeline .timelinePublisher, .touched, .sharerAttachment,
-.item a.primary.touched .primarywrap, ._537a, ._7cui,
-._5xo2, ._5u5a::before, ._4u3j, ._15ks, ._5hua, ._59tt, ._41ft, .jx-tokenizer, ._55fj,
-.excessItem, .acr, ._5-lx, ._3g9-, ._55ws, ._6dsj ._3gin, ._69aj,
-._4e8n, ._5pxa._3uj9, ._5n_5, ._u2d, ._56bu::before, ._5h8f, ._d00, ._2066, ._2k51,
-._10sb li.selected, ._2z4j, ._ib-, ._1bhl, ._5a5j, ._6--d, ._77p7,
-._2b06, ._2tsf, ._3gka, .mCount, ._27vc, ._4pv-, ._6pk5,
-._4qax, ._4756, ._w34, ._56bv::before, ._5769, ._34iv, ._z-w, ._t21, .mToken,
-#addMembersTypeahead .mToken.mTokenWeakReference,
-.acbk {
- background: rgba(199, 70, 70, 0.35) !important;
-}
-
-.mQuestionsPollResultsBar .shaded {
- background: #980008 !important;
-}
-
-._220g, ._1_y8:after, ._6pk6,
-._2zh4::before, ._2ip_ ._2zh4::before, ._2ip_ ._15kk::before, ._2ip_ ._15kk + ._4u3j::before,
-._58a0:before, ._43mh::before, ._43mh::after, ._1_-1::before, ._1kmv:after, ._1_ac:before {
- background: rgba(215, 176, 215, 0.3) !important;
-}
-
-button ._v89 ._54k8._1fl1 {
- background: #980008 !important;
-}
-
-._15kl::before, ._37fd .inlineComposerButton, ._1hb:before,
-._5j35::after, ._2k4b, ._3to7, ._4nw8 {
- border-left: 1px solid rgba(215, 176, 215, 0.3) !important;
-}
-
-._4_d1, ._5cni, ._3jcq {
- border-right: 1px solid rgba(215, 176, 215, 0.3) !important;
-}
-
-._1mx0, ._1rbr, ._5yt8, ._idb, ._cld, ._1e8h, ._z-w, ._1ha, ._1n8h ._1oby, ._5f99, ._2t39,
-._2pbp, ._5rou:first-child, ._egf:first-child, ._io2, ._3qdi ._48_m::after, ._46dd::before,
-._15n_, ._3-2-, ._27ve, ._2s20, ._gui, ._2s21 > *::after, ._32qk, ._d00, ._d01, ._38o9,
-._3u9t, ._55fj, .mEventProfileSection.useBorder td, ._3ils, ._5as0, ._5as2, ._5-lw,
-._52x1, ._3wjp, ._usq, ._2cul:before, ._13e_, .jewel .flyout, ._3bg5 ._52x6, ._56d8, .al {
- border-top: 1px solid rgba(215, 176, 215, 0.3) !important;
-}
-
-._15ny::after, ._z-w, ._8i2, ._2nk0, ._22_8, ._1t4h, ._37fd, ._1ha, ._3bg5 ._56do, ._8he,
-._400s, ._5hoc, ._1bhn, ._5ag6, ._4pvz,
-._301x, ._x08 ._x0a:after, ._36dc, ._6-l ._57jn, ._527k, ._g_k,
-._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, ._2bdc, ._3ci9,
-.mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._38do, ._38dt,
-._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .error, ._35--, ._1wev,
-.jx-result, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._5rgs, ._5xuj, ._1sv1, ._idb,
-._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._5hu6,
-._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc,
-._5pz4, ._5lp4, ._5lp5, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child,
-._5fjw > :first-child {
- border-bottom: 1px solid rgba(215, 176, 215, 0.3) !important;
-}
-
-.item a.primary.touched .primarywrap, ._4dwt ._5y33, ._1ih_, ._5_50, ._6beq, ._69aj,
-._5fjv, ._3on6, ._2u4w, ._2om3, ._2ol-, ._5fjw, ._4z83, ._1gkq, ._4-dy {
- border-top: 1px solid rgba(215, 176, 215, 0.3) !important;
- border-bottom: 1px solid rgba(215, 176, 215, 0.3) !important;
-}
-
-._d4i, ._f6s, .mentions-suggest-item, .mentions-suggest, .sharerAttachment,
-.mToken, #addMembersTypeahead .mToken.mTokenWeakReference, .mQuestionsPollResultsBar,
-._15q7, ._2q7v, ._4dwt ._16ii, ._3qdi::after,
-._2q7w, .acy, ._58ak, ._3t_l, ._4msa, ._3h8i, ._3clk, ._1kt6, ._1ksq,
-._1_y5, ._lr0, ._5hgt, ._2cpp, ._50uu, ._50uw, ._31yd, ._1e3d, ._3xz7, ._1xoz,
-._4kcb, ._2lut, .jewel .touchable-notification.touched, .touchable-notification .touchable.touched,
-.home-notification .touchable.touched, ._6beo ._6ber,
-._73ku ._73jw, ._6--d, ._26vk._56bt,
-._4e8n, ._uww, .mentions-placeholder, .mentions-shadow, .mentions-measurer,
-._5whq, ._59tt, ._41ft::after, .jx-tokenizer, ._3uqf, ._4756, ._1rrd, ._5n_f {
- border: 1px solid rgba(215, 176, 215, 0.3) !important;
-}
-
-.mQuestionsPollResultsBar .shaded, ._1027._13sm {
- border: 1px solid #d7b0d7 !important;
-}
-
-._3gka {
- border: 1px dashed rgba(215, 176, 215, 0.3) !important;
-}
-
-._4o58::after, .acr, ._t21, ._2bdb,
-.acw, .aclb, ._4qax, ._5h8f {
- border-color: rgba(215, 176, 215, 0.3) !important;
-}
-
-._15ks ._15kl::before {
- border-left: 1px solid transparent !important;
-}
-
-._56bf, .touch .btn {
- border-radius: 0 !important;
- border: 0 !important;
-}
-
-._2cis {
- border-left: 10px solid rgba(255, 0, 255, 0.02) !important;
- border-right: 10px solid rgba(255, 0, 255, 0.02) !important;
-}
-
-._2cir.selected, ._42rv, ._5zma, ._2x2s {
- border-bottom: 3px solid #d7b0d7 !important;
-}
-
-._1ss6 {
- border-left: 2px solid #d7b0d7 !important;
-}
-
-._484w.selected > ._6zf, ._5kqs::after, ._3lvo ._5xum._5xuk, ._x0b {
- border-bottom: 1px solid #d7b0d7 !important;
-}
-
-._34ee {
- background: rgba(199, 70, 70, 0.35) !important;
- color: #d7b0d7 !important;
-}
-
-._34em ._34ee {
- background: #980008 !important;
- color: #76d7c2 !important;
-}
-
-._5as0, ._5cni, ._5as2 {
- background: #451515 !important;
-}
-
-*, *::after, *::before {
- text-shadow: none !important;
- box-shadow: none !important;
-}
-
-[data-sigil=m_login_upsell],
-[data-sigil="m-loading-indicator-animate m-loading-indicator-root"] {
- display: none !important;
-}
-
-::-webkit-input-placeholder {
- color: #d7b0d7 !important;
-}
-
-:-moz-placeholder {
- color: #d7b0d7 !important;
-}
-
-::-moz-placeholder {
- color: #d7b0d7 !important;
-}
-
-:-ms-input-placeholder {
- color: #d7b0d7 !important;
-}
-
-.excessItem {
- outline: rgba(215, 176, 215, 0.3) !important;
-}
-
-._3m1m {
- background: linear-gradient(transparent, #451515) !important;
-}
-
-@-webkit-keyframes highlightFade {
- 0%, 50% {
- background: rgba(199, 70, 70, 0.35);
- }
- 100% {
- background: rgba(255, 0, 255, 0.02);
- }
-}
-@-moz-keyframes highlightFade {
- 0%, 50% {
- background: rgba(199, 70, 70, 0.35);
- }
- 100% {
- background: rgba(255, 0, 255, 0.02);
- }
-}
-@keyframes highlightFade {
- 0%, 50% {
- background: rgba(199, 70, 70, 0.35);
- }
- 100% {
- background: rgba(255, 0, 255, 0.02);
- }
-}
-@-webkit-keyframes chatHighlightAnimation {
- 0%, 100% {
- background: rgba(255, 0, 255, 0.02);
- }
- 50% {
- background: rgba(199, 70, 70, 0.35);
- }
-}
-@-moz-keyframes chatHighlightAnimation {
- 0%, 100% {
- background: rgba(255, 0, 255, 0.02);
- }
- 50% {
- background: rgba(199, 70, 70, 0.35);
- }
-}
-@keyframes chatHighlightAnimation {
- 0%, 100% {
- background: rgba(255, 0, 255, 0.02);
- }
- 50% {
- background: rgba(199, 70, 70, 0.35);
- }
-}
diff --git a/app/src/web/assets/css/themes/custom.css b/app/src/web/assets/css/themes/custom.css
deleted file mode 100644
index 9d408971..00000000
--- a/app/src/web/assets/css/themes/custom.css
+++ /dev/null
@@ -1,339 +0,0 @@
-body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5,
-._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk,
-.touched *, ._1_yj, ._1_yl, ._4pj9, ._2bdc, ._3qdh ._3qdn ._3qdk, ._3qdk ._48_q,
-._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, .cq, ._usr,
-._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5,
-._1lf4, ._1hiz, ._xod, ._5ag5, ._zmk, ._3t_h, ._5lm6, ._3clv, ._3zlc, ._36rd,
-._31zk, ._31zl, ._3xsa, ._3xs9, ._2-4s, ._2fzz ul, ._3z10, ._4mo, ._2om6,
-._43mh, .touch .btn, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35,
-._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu,
-._18qg, ._1_ac, ._529p, ._4dwt ._1vh3, ._4a5f, ._23_t, ._2rzc, ._23_s, ._2rzd,
-._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._36e0, ._4-dy,
-._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, .mCount, ._27vc, ._24e1, ._2rbw, ._3iyw ._3mzw,
-textarea:not([style*="color: rgb"]), ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782,
-._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._6--d, ._77p7, ._7h_g,
-._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._38d-, ._3n8y, ._38dt, ._3oyg, ._21dc,
-._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._6xqt, ._7cui,
-._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._7cdj,
-._2new, .appCenterCategorySelectorButton, ._1ksq, ._1kt6, ._6ber, ._mxb, ._3oyd, ._3gir, ._3gis,
-div.sharerSelector, .footer, ._4pv_, ._1dbp, ._3kad, ._20zc, ._2i5v, ._2i5w,
-a, ._5fpq, ._4gux, ._3bg5 ._52x1, ._3bg5 ._52x2, ._6dsj ._3gin, ._hdn._hdn,
-.mentions-input:not([style*="color: rgb"]), .mentions-placeholder:not([style*="color: rgb"]),
-.largeStatusBox .placeHolder, .fcw, ._2rgt, ._67i4 ._5hu6 ._59tt,
-._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4,
-._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs,
-h1, h2, h3, h4, h5, h6 {
- color: $T$ !important;
-}
-
-strong > a, ._15ks ._2q8z._2q8z, ._1e3e, .blueName, ._5kqs ._55sr {
- color: $A$ !important;
-}
-
-._42nf ._42ng {
- color: transparent !important;
-}
-
-p > a, .msg span > a {
- color: $TT$ !important;
-}
-
-#viewport {
- background: $B$ !important;
-}
-
-body, :root, #root, #header, #MComposer, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4,
-._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._3qdh, ._8ca, ._3h8i,
-._6-l ._2us7, ._6-l ._6-p:not([style*="background-image:"]), ._333v, div.sharerSelector, ._529j, ._305j, ._1pph, ._3t_l, ._4pvz,
-._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._24e1, ._-j7,
-._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz,
-._38do, .bo, .cq, ._234-, ._a-5, ._2zh4, ._15ks, ._3oyc, ._36dc, ._3iyw ._3iyx, ._6bes, ._55wo, ._4-dy,
-.tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0,
-.al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._2bdb, ._3ci9,
-._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._6be7,
-._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._5xp2, div.fullwidthMore,
-._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._voz, ._vos,
-._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5_ee, ._3clk,
-._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5,
-._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, ._3tl8, ._65wz, ._4edl,
-.acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._1glm,
-._ue6, ._hdn._hdn, ._6vzw, ._77xj, ._38nq, ._9_7, ._51li,
-._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3bmj, ._5zmb, ._2x2s, ._3kac, ._3kad,
-._3f50, .mentions-placeholder, .mentions, .mentions-measurer, .acg, ._59tu,
-._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._1dbp, ._5zma, ._6beq, ._vi6,
-._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy,
-._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x {
- background: $BT$ !important;
-}
-
-._31nf, ._2v9s, ._d4i, article._55wo, ._10c_, ._2jl2, ._6150, ._50mi, ._4-dw, ._4_2z, ._5m_s, ._13fn {
- background: $C$ !important;
-}
-
-.aclb {
- background: $TI$ !important;
-}
-
-._cv_, ._2sq8 {
- background-color: $BT$ !important;
-}
-
-#page, ._8l7, ._-j8, ._-j9, ._6o5v, ._uwx, .touch ._uwx.mentions-input {
- background: transparent !important;
-}
-
-.jewel, .flyout, ._52z5, ._13e_, ._5-lw, ._5c0e, .jx-result, ._336p, .mentions-suggest-item, ._2suk,
-.mentions-suggest, ._1xoz, ._1xow {
- background: $O$ !important;
-}
-
-._403n, ._14v5 ._14v8, ._1-kc {
- background: $OO$ !important;
-}
-
-button:not([style*=image]):not(.privacyButtons), button::before, .touch ._56bt, ._56be::before, .btnS, .touch::before,
-._590n, ._4g8h, ._2cpp, ._58a0.touched:after,
-.timeline .timelinePublisher, .touched, .sharerAttachment,
-.item a.primary.touched .primarywrap, ._537a, ._7cui,
-._5xo2, ._5u5a::before, ._4u3j, ._15ks, ._5hua, ._59tt, ._41ft, .jx-tokenizer, ._55fj,
-.excessItem, .acr, ._5-lx, ._3g9-, ._55ws, ._6dsj ._3gin, ._69aj,
-._4e8n, ._5pxa._3uj9, ._5n_5, ._u2d, ._56bu::before, ._5h8f, ._d00, ._2066, ._2k51,
-._10sb li.selected, ._2z4j, ._ib-, ._1bhl, ._5a5j, ._6--d, ._77p7,
-._2b06, ._2tsf, ._3gka, .mCount, ._27vc, ._4pv-, ._6pk5,
-._4qax, ._4756, ._w34, ._56bv::before, ._5769, ._34iv, ._z-w, ._t21, .mToken,
-#addMembersTypeahead .mToken.mTokenWeakReference,
-.acbk {
- background: $BBT$ !important;
-}
-
-.mQuestionsPollResultsBar .shaded {
- background: $A$ !important;
-}
-
-._220g, ._1_y8:after, ._6pk6,
-._2zh4::before, ._2ip_ ._2zh4::before, ._2ip_ ._15kk::before, ._2ip_ ._15kk + ._4u3j::before,
-._58a0:before, ._43mh::before, ._43mh::after, ._1_-1::before, ._1kmv:after, ._1_ac:before {
- background: $D$ !important;
-}
-
-button ._v89 ._54k8._1fl1 {
- background: $A$ !important;
-}
-
-._15kl::before, ._37fd .inlineComposerButton, ._1hb:before,
-._5j35::after, ._2k4b, ._3to7, ._4nw8 {
- border-left: 1px solid $D$ !important;
-}
-
-._4_d1, ._5cni, ._3jcq {
- border-right: 1px solid $D$ !important;
-}
-
-._1mx0, ._1rbr, ._5yt8, ._idb, ._cld, ._1e8h, ._z-w, ._1ha, ._1n8h ._1oby, ._5f99, ._2t39,
-._2pbp, ._5rou:first-child, ._egf:first-child, ._io2, ._3qdi ._48_m::after, ._46dd::before,
-._15n_, ._3-2-, ._27ve, ._2s20, ._gui, ._2s21 > *::after, ._32qk, ._d00, ._d01, ._38o9,
-._3u9t, ._55fj, .mEventProfileSection.useBorder td, ._3ils, ._5as0, ._5as2, ._5-lw,
-._52x1, ._3wjp, ._usq, ._2cul:before, ._13e_, .jewel .flyout, ._3bg5 ._52x6, ._56d8, .al {
- border-top: 1px solid $D$ !important;
-}
-
-._15ny::after, ._z-w, ._8i2, ._2nk0, ._22_8, ._1t4h, ._37fd, ._1ha, ._3bg5 ._56do, ._8he,
-._400s, ._5hoc, ._1bhn, ._5ag6, ._4pvz,
-._301x, ._x08 ._x0a:after, ._36dc, ._6-l ._57jn, ._527k, ._g_k,
-._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, ._2bdc, ._3ci9,
-.mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._38do, ._38dt,
-._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .error, ._35--, ._1wev,
-.jx-result, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._5rgs, ._5xuj, ._1sv1, ._idb,
-._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._5hu6,
-._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc,
-._5pz4, ._5lp4, ._5lp5, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child,
-._5fjw > :first-child {
- border-bottom: 1px solid $D$ !important;
-}
-
-.item a.primary.touched .primarywrap, ._4dwt ._5y33, ._1ih_, ._5_50, ._6beq, ._69aj,
-._5fjv, ._3on6, ._2u4w, ._2om3, ._2ol-, ._5fjw, ._4z83, ._1gkq, ._4-dy {
- border-top: 1px solid $D$ !important;
- border-bottom: 1px solid $D$ !important;
-}
-
-._d4i, ._f6s, .mentions-suggest-item, .mentions-suggest, .sharerAttachment,
-.mToken, #addMembersTypeahead .mToken.mTokenWeakReference, .mQuestionsPollResultsBar,
-._15q7, ._2q7v, ._4dwt ._16ii, ._3qdi::after,
-._2q7w, .acy, ._58ak, ._3t_l, ._4msa, ._3h8i, ._3clk, ._1kt6, ._1ksq,
-._1_y5, ._lr0, ._5hgt, ._2cpp, ._50uu, ._50uw, ._31yd, ._1e3d, ._3xz7, ._1xoz,
-._4kcb, ._2lut, .jewel .touchable-notification.touched, .touchable-notification .touchable.touched,
-.home-notification .touchable.touched, ._6beo ._6ber,
-._73ku ._73jw, ._6--d, ._26vk._56bt,
-._4e8n, ._uww, .mentions-placeholder, .mentions-shadow, .mentions-measurer,
-._5whq, ._59tt, ._41ft::after, .jx-tokenizer, ._3uqf, ._4756, ._1rrd, ._5n_f {
- border: 1px solid $D$ !important;
-}
-
-.mQuestionsPollResultsBar .shaded, ._1027._13sm {
- border: 1px solid $T$ !important;
-}
-
-._3gka {
- border: 1px dashed $D$ !important;
-}
-
-._4o58::after, .acr, ._t21, ._2bdb,
-.acw, .aclb, ._4qax, ._5h8f {
- border-color: $D$ !important;
-}
-
-._15ks ._15kl::before {
- border-left: 1px solid transparent !important;
-}
-
-._56bf, .touch .btn {
- border-radius: 0 !important;
- border: 0 !important;
-}
-
-._2cis {
- border-left: 10px solid $BT$ !important;
- border-right: 10px solid $BT$ !important;
-}
-
-._2cir.selected, ._42rv, ._5zma, ._2x2s {
- border-bottom: 3px solid $T$ !important;
-}
-
-._1ss6 {
- border-left: 2px solid $T$ !important;
-}
-
-._484w.selected > ._6zf, ._5kqs::after, ._3lvo ._5xum._5xuk, ._x0b {
- border-bottom: 1px solid $T$ !important;
-}
-
-._34ee {
- background: $BBT$ !important;
- color: $T$ !important;
-}
-
-._34em ._34ee {
- background: $A$ !important;
- color: $AT$ !important;
-}
-
-._5as0, ._5cni, ._5as2 {
- background: $O$ !important;
-}
-
-*, *::after, *::before {
- text-shadow: none !important;
- box-shadow: none !important;
-}
-
-[data-sigil=m_login_upsell],
-[data-sigil="m-loading-indicator-animate m-loading-indicator-root"] {
- display: none !important;
-}
-
-::-webkit-input-placeholder {
- color: $T$ !important;
-}
-
-:-moz-placeholder {
- color: $T$ !important;
-}
-
-::-moz-placeholder {
- color: $T$ !important;
-}
-
-:-ms-input-placeholder {
- color: $T$ !important;
-}
-
-.excessItem {
- outline: $D$ !important;
-}
-
-._3m1m {
- background: linear-gradient(transparent, $O$) !important;
-}
-
-@-webkit-keyframes highlightFade {
- 0%, 50% {
- background: $BBT$;
- }
- 100% {
- background: $BT$;
- }
-}
-@-moz-keyframes highlightFade {
- 0%, 50% {
- background: $BBT$;
- }
- 100% {
- background: $BT$;
- }
-}
-@keyframes highlightFade {
- 0%, 50% {
- background: $BBT$;
- }
- 100% {
- background: $BT$;
- }
-}
-@-webkit-keyframes chatHighlightAnimation {
- 0%, 100% {
- background: $BT$;
- }
- 50% {
- background: $BBT$;
- }
-}
-@-moz-keyframes chatHighlightAnimation {
- 0%, 100% {
- background: $BT$;
- }
- 50% {
- background: $BBT$;
- }
-}
-@keyframes chatHighlightAnimation {
- 0%, 100% {
- background: $BT$;
- }
- 50% {
- background: $BBT$;
- }
-}
-._50uu {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="$T$" viewBox="0 -10 50 50"%3E%3Ccircle cx="25" cy="23" r="3.2"/%3E%3Cpath d="M22 13l-1.83 2H17c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V17c0-1.1-.9-2-2-2h-3.17L28 13h-6zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"/%3E%3Cpath fill="none" d="M13 11h24v24H13z"/%3E%3C/svg%3E') no-repeat !important;
-}
-
-._50uw {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="$T$" viewBox="0 0 50 50"%3E%3Cpath fill="none" d="M13 26h24v24H13z"/%3E%3Cpath d="M30 31.5V28c0-.55-.45-1-1-1H17c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/%3E%3C/svg%3E') no-repeat !important;
-}
-
-._15km ._15ko::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="$T$" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1.91l-.01-.01L23 10z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15ko._77la::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="$A$" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1.91l-.01-.01L23 10z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15kq::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="$T$" viewBox="0 0 24 24"%3E%3Cpath d="M21.99 4c0-1.1-.89-2-1.99-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4-.01-18z"/%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15kr::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="$T$" viewBox="0 0 24 24"%3E%3Cpath d="M14 9V5l7 7-7 7v-4.1c-5 0-8.5 1.6-11 5.1 1-5 4-10 11-11z"/%3E%3Cpath fill="none" d="M24 0H0v24h24z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-.story_body_container i.img[data-sigil*=story-popup-context] {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath fill="$T$" d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
diff --git a/app/src/web/assets/css/themes/material_amoled.css b/app/src/web/assets/css/themes/material_amoled.css
deleted file mode 100644
index 6cf12e2b..00000000
--- a/app/src/web/assets/css/themes/material_amoled.css
+++ /dev/null
@@ -1,339 +0,0 @@
-body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5,
-._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk,
-.touched *, ._1_yj, ._1_yl, ._4pj9, ._2bdc, ._3qdh ._3qdn ._3qdk, ._3qdk ._48_q,
-._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, .cq, ._usr,
-._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5,
-._1lf4, ._1hiz, ._xod, ._5ag5, ._zmk, ._3t_h, ._5lm6, ._3clv, ._3zlc, ._36rd,
-._31zk, ._31zl, ._3xsa, ._3xs9, ._2-4s, ._2fzz ul, ._3z10, ._4mo, ._2om6,
-._43mh, .touch .btn, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35,
-._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu,
-._18qg, ._1_ac, ._529p, ._4dwt ._1vh3, ._4a5f, ._23_t, ._2rzc, ._23_s, ._2rzd,
-._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._36e0, ._4-dy,
-._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, .mCount, ._27vc, ._24e1, ._2rbw, ._3iyw ._3mzw,
-textarea:not([style*="color: rgb"]), ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782,
-._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._6--d, ._77p7, ._7h_g,
-._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._38d-, ._3n8y, ._38dt, ._3oyg, ._21dc,
-._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._6xqt, ._7cui,
-._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._7cdj,
-._2new, .appCenterCategorySelectorButton, ._1ksq, ._1kt6, ._6ber, ._mxb, ._3oyd, ._3gir, ._3gis,
-div.sharerSelector, .footer, ._4pv_, ._1dbp, ._3kad, ._20zc, ._2i5v, ._2i5w,
-a, ._5fpq, ._4gux, ._3bg5 ._52x1, ._3bg5 ._52x2, ._6dsj ._3gin, ._hdn._hdn,
-.mentions-input:not([style*="color: rgb"]), .mentions-placeholder:not([style*="color: rgb"]),
-.largeStatusBox .placeHolder, .fcw, ._2rgt, ._67i4 ._5hu6 ._59tt,
-._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4,
-._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs,
-h1, h2, h3, h4, h5, h6 {
- color: #fff !important;
-}
-
-strong > a, ._15ks ._2q8z._2q8z, ._1e3e, .blueName, ._5kqs ._55sr {
- color: #5d86dd !important;
-}
-
-._42nf ._42ng {
- color: transparent !important;
-}
-
-p > a, .msg span > a {
- color: #5d86dd !important;
-}
-
-#viewport {
- background: #000 !important;
-}
-
-body, :root, #root, #header, #MComposer, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4,
-._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._3qdh, ._8ca, ._3h8i,
-._6-l ._2us7, ._6-l ._6-p:not([style*="background-image:"]), ._333v, div.sharerSelector, ._529j, ._305j, ._1pph, ._3t_l, ._4pvz,
-._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._24e1, ._-j7,
-._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz,
-._38do, .bo, .cq, ._234-, ._a-5, ._2zh4, ._15ks, ._3oyc, ._36dc, ._3iyw ._3iyx, ._6bes, ._55wo, ._4-dy,
-.tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0,
-.al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._2bdb, ._3ci9,
-._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._6be7,
-._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._5xp2, div.fullwidthMore,
-._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._voz, ._vos,
-._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5_ee, ._3clk,
-._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5,
-._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, ._3tl8, ._65wz, ._4edl,
-.acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._1glm,
-._ue6, ._hdn._hdn, ._6vzw, ._77xj, ._38nq, ._9_7, ._51li,
-._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3bmj, ._5zmb, ._2x2s, ._3kac, ._3kad,
-._3f50, .mentions-placeholder, .mentions, .mentions-measurer, .acg, ._59tu,
-._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._1dbp, ._5zma, ._6beq, ._vi6,
-._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy,
-._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x {
- background: #000 !important;
-}
-
-._31nf, ._2v9s, ._d4i, article._55wo, ._10c_, ._2jl2, ._6150, ._50mi, ._4-dw, ._4_2z, ._5m_s, ._13fn {
- background: rgba(0, 0, 0, 0.35) !important;
-}
-
-.aclb {
- background: rgba(255, 255, 255, 0.2) !important;
-}
-
-._cv_, ._2sq8 {
- background-color: #000 !important;
-}
-
-#page, ._8l7, ._-j8, ._-j9, ._6o5v, ._uwx, .touch ._uwx.mentions-input {
- background: transparent !important;
-}
-
-.jewel, .flyout, ._52z5, ._13e_, ._5-lw, ._5c0e, .jx-result, ._336p, .mentions-suggest-item, ._2suk,
-.mentions-suggest, ._1xoz, ._1xow {
- background: black !important;
-}
-
-._403n, ._14v5 ._14v8, ._1-kc {
- background: black !important;
-}
-
-button:not([style*=image]):not(.privacyButtons), button::before, .touch ._56bt, ._56be::before, .btnS, .touch::before,
-._590n, ._4g8h, ._2cpp, ._58a0.touched:after,
-.timeline .timelinePublisher, .touched, .sharerAttachment,
-.item a.primary.touched .primarywrap, ._537a, ._7cui,
-._5xo2, ._5u5a::before, ._4u3j, ._15ks, ._5hua, ._59tt, ._41ft, .jx-tokenizer, ._55fj,
-.excessItem, .acr, ._5-lx, ._3g9-, ._55ws, ._6dsj ._3gin, ._69aj,
-._4e8n, ._5pxa._3uj9, ._5n_5, ._u2d, ._56bu::before, ._5h8f, ._d00, ._2066, ._2k51,
-._10sb li.selected, ._2z4j, ._ib-, ._1bhl, ._5a5j, ._6--d, ._77p7,
-._2b06, ._2tsf, ._3gka, .mCount, ._27vc, ._4pv-, ._6pk5,
-._4qax, ._4756, ._w34, ._56bv::before, ._5769, ._34iv, ._z-w, ._t21, .mToken,
-#addMembersTypeahead .mToken.mTokenWeakReference,
-.acbk {
- background: rgba(0, 0, 0, 0.35) !important;
-}
-
-.mQuestionsPollResultsBar .shaded {
- background: #5d86dd !important;
-}
-
-._220g, ._1_y8:after, ._6pk6,
-._2zh4::before, ._2ip_ ._2zh4::before, ._2ip_ ._15kk::before, ._2ip_ ._15kk + ._4u3j::before,
-._58a0:before, ._43mh::before, ._43mh::after, ._1_-1::before, ._1kmv:after, ._1_ac:before {
- background: rgba(255, 255, 255, 0.3) !important;
-}
-
-button ._v89 ._54k8._1fl1 {
- background: #5d86dd !important;
-}
-
-._15kl::before, ._37fd .inlineComposerButton, ._1hb:before,
-._5j35::after, ._2k4b, ._3to7, ._4nw8 {
- border-left: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-._4_d1, ._5cni, ._3jcq {
- border-right: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-._1mx0, ._1rbr, ._5yt8, ._idb, ._cld, ._1e8h, ._z-w, ._1ha, ._1n8h ._1oby, ._5f99, ._2t39,
-._2pbp, ._5rou:first-child, ._egf:first-child, ._io2, ._3qdi ._48_m::after, ._46dd::before,
-._15n_, ._3-2-, ._27ve, ._2s20, ._gui, ._2s21 > *::after, ._32qk, ._d00, ._d01, ._38o9,
-._3u9t, ._55fj, .mEventProfileSection.useBorder td, ._3ils, ._5as0, ._5as2, ._5-lw,
-._52x1, ._3wjp, ._usq, ._2cul:before, ._13e_, .jewel .flyout, ._3bg5 ._52x6, ._56d8, .al {
- border-top: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-._15ny::after, ._z-w, ._8i2, ._2nk0, ._22_8, ._1t4h, ._37fd, ._1ha, ._3bg5 ._56do, ._8he,
-._400s, ._5hoc, ._1bhn, ._5ag6, ._4pvz,
-._301x, ._x08 ._x0a:after, ._36dc, ._6-l ._57jn, ._527k, ._g_k,
-._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, ._2bdc, ._3ci9,
-.mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._38do, ._38dt,
-._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .error, ._35--, ._1wev,
-.jx-result, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._5rgs, ._5xuj, ._1sv1, ._idb,
-._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._5hu6,
-._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc,
-._5pz4, ._5lp4, ._5lp5, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child,
-._5fjw > :first-child {
- border-bottom: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-.item a.primary.touched .primarywrap, ._4dwt ._5y33, ._1ih_, ._5_50, ._6beq, ._69aj,
-._5fjv, ._3on6, ._2u4w, ._2om3, ._2ol-, ._5fjw, ._4z83, ._1gkq, ._4-dy {
- border-top: 1px solid rgba(255, 255, 255, 0.3) !important;
- border-bottom: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-._d4i, ._f6s, .mentions-suggest-item, .mentions-suggest, .sharerAttachment,
-.mToken, #addMembersTypeahead .mToken.mTokenWeakReference, .mQuestionsPollResultsBar,
-._15q7, ._2q7v, ._4dwt ._16ii, ._3qdi::after,
-._2q7w, .acy, ._58ak, ._3t_l, ._4msa, ._3h8i, ._3clk, ._1kt6, ._1ksq,
-._1_y5, ._lr0, ._5hgt, ._2cpp, ._50uu, ._50uw, ._31yd, ._1e3d, ._3xz7, ._1xoz,
-._4kcb, ._2lut, .jewel .touchable-notification.touched, .touchable-notification .touchable.touched,
-.home-notification .touchable.touched, ._6beo ._6ber,
-._73ku ._73jw, ._6--d, ._26vk._56bt,
-._4e8n, ._uww, .mentions-placeholder, .mentions-shadow, .mentions-measurer,
-._5whq, ._59tt, ._41ft::after, .jx-tokenizer, ._3uqf, ._4756, ._1rrd, ._5n_f {
- border: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-.mQuestionsPollResultsBar .shaded, ._1027._13sm {
- border: 1px solid #fff !important;
-}
-
-._3gka {
- border: 1px dashed rgba(255, 255, 255, 0.3) !important;
-}
-
-._4o58::after, .acr, ._t21, ._2bdb,
-.acw, .aclb, ._4qax, ._5h8f {
- border-color: rgba(255, 255, 255, 0.3) !important;
-}
-
-._15ks ._15kl::before {
- border-left: 1px solid transparent !important;
-}
-
-._56bf, .touch .btn {
- border-radius: 0 !important;
- border: 0 !important;
-}
-
-._2cis {
- border-left: 10px solid #000 !important;
- border-right: 10px solid #000 !important;
-}
-
-._2cir.selected, ._42rv, ._5zma, ._2x2s {
- border-bottom: 3px solid #fff !important;
-}
-
-._1ss6 {
- border-left: 2px solid #fff !important;
-}
-
-._484w.selected > ._6zf, ._5kqs::after, ._3lvo ._5xum._5xuk, ._x0b {
- border-bottom: 1px solid #fff !important;
-}
-
-._34ee {
- background: rgba(0, 0, 0, 0.35) !important;
- color: #fff !important;
-}
-
-._34em ._34ee {
- background: #5d86dd !important;
- color: #fff !important;
-}
-
-._5as0, ._5cni, ._5as2 {
- background: black !important;
-}
-
-*, *::after, *::before {
- text-shadow: none !important;
- box-shadow: none !important;
-}
-
-[data-sigil=m_login_upsell],
-[data-sigil="m-loading-indicator-animate m-loading-indicator-root"] {
- display: none !important;
-}
-
-::-webkit-input-placeholder {
- color: #fff !important;
-}
-
-:-moz-placeholder {
- color: #fff !important;
-}
-
-::-moz-placeholder {
- color: #fff !important;
-}
-
-:-ms-input-placeholder {
- color: #fff !important;
-}
-
-.excessItem {
- outline: rgba(255, 255, 255, 0.3) !important;
-}
-
-._3m1m {
- background: linear-gradient(transparent, black) !important;
-}
-
-@-webkit-keyframes highlightFade {
- 0%, 50% {
- background: rgba(0, 0, 0, 0.35);
- }
- 100% {
- background: #000;
- }
-}
-@-moz-keyframes highlightFade {
- 0%, 50% {
- background: rgba(0, 0, 0, 0.35);
- }
- 100% {
- background: #000;
- }
-}
-@keyframes highlightFade {
- 0%, 50% {
- background: rgba(0, 0, 0, 0.35);
- }
- 100% {
- background: #000;
- }
-}
-@-webkit-keyframes chatHighlightAnimation {
- 0%, 100% {
- background: #000;
- }
- 50% {
- background: rgba(0, 0, 0, 0.35);
- }
-}
-@-moz-keyframes chatHighlightAnimation {
- 0%, 100% {
- background: #000;
- }
- 50% {
- background: rgba(0, 0, 0, 0.35);
- }
-}
-@keyframes chatHighlightAnimation {
- 0%, 100% {
- background: #000;
- }
- 50% {
- background: rgba(0, 0, 0, 0.35);
- }
-}
-._50uu {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 -10 50 50"%3E%3Ccircle cx="25" cy="23" r="3.2"/%3E%3Cpath d="M22 13l-1.83 2H17c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V17c0-1.1-.9-2-2-2h-3.17L28 13h-6zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"/%3E%3Cpath fill="none" d="M13 11h24v24H13z"/%3E%3C/svg%3E') no-repeat !important;
-}
-
-._50uw {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 0 50 50"%3E%3Cpath fill="none" d="M13 26h24v24H13z"/%3E%3Cpath d="M30 31.5V28c0-.55-.45-1-1-1H17c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/%3E%3C/svg%3E') no-repeat !important;
-}
-
-._15km ._15ko::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1.91l-.01-.01L23 10z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15ko._77la::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%235d86dd" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1.91l-.01-.01L23 10z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15kq::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 0 24 24"%3E%3Cpath d="M21.99 4c0-1.1-.89-2-1.99-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4-.01-18z"/%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15kr::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 0 24 24"%3E%3Cpath d="M14 9V5l7 7-7 7v-4.1c-5 0-8.5 1.6-11 5.1 1-5 4-10 11-11z"/%3E%3Cpath fill="none" d="M24 0H0v24h24z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-.story_body_container i.img[data-sigil*=story-popup-context] {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath fill="%23fff" d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
diff --git a/app/src/web/assets/css/themes/material_dark.css b/app/src/web/assets/css/themes/material_dark.css
deleted file mode 100644
index b9799018..00000000
--- a/app/src/web/assets/css/themes/material_dark.css
+++ /dev/null
@@ -1,339 +0,0 @@
-body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5,
-._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk,
-.touched *, ._1_yj, ._1_yl, ._4pj9, ._2bdc, ._3qdh ._3qdn ._3qdk, ._3qdk ._48_q,
-._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, .cq, ._usr,
-._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5,
-._1lf4, ._1hiz, ._xod, ._5ag5, ._zmk, ._3t_h, ._5lm6, ._3clv, ._3zlc, ._36rd,
-._31zk, ._31zl, ._3xsa, ._3xs9, ._2-4s, ._2fzz ul, ._3z10, ._4mo, ._2om6,
-._43mh, .touch .btn, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35,
-._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu,
-._18qg, ._1_ac, ._529p, ._4dwt ._1vh3, ._4a5f, ._23_t, ._2rzc, ._23_s, ._2rzd,
-._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._36e0, ._4-dy,
-._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, .mCount, ._27vc, ._24e1, ._2rbw, ._3iyw ._3mzw,
-textarea:not([style*="color: rgb"]), ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782,
-._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._6--d, ._77p7, ._7h_g,
-._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._38d-, ._3n8y, ._38dt, ._3oyg, ._21dc,
-._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._6xqt, ._7cui,
-._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._7cdj,
-._2new, .appCenterCategorySelectorButton, ._1ksq, ._1kt6, ._6ber, ._mxb, ._3oyd, ._3gir, ._3gis,
-div.sharerSelector, .footer, ._4pv_, ._1dbp, ._3kad, ._20zc, ._2i5v, ._2i5w,
-a, ._5fpq, ._4gux, ._3bg5 ._52x1, ._3bg5 ._52x2, ._6dsj ._3gin, ._hdn._hdn,
-.mentions-input:not([style*="color: rgb"]), .mentions-placeholder:not([style*="color: rgb"]),
-.largeStatusBox .placeHolder, .fcw, ._2rgt, ._67i4 ._5hu6 ._59tt,
-._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4,
-._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs,
-h1, h2, h3, h4, h5, h6 {
- color: #fff !important;
-}
-
-strong > a, ._15ks ._2q8z._2q8z, ._1e3e, .blueName, ._5kqs ._55sr {
- color: #5d86dd !important;
-}
-
-._42nf ._42ng {
- color: transparent !important;
-}
-
-p > a, .msg span > a {
- color: #5d86dd !important;
-}
-
-#viewport {
- background: #303030 !important;
-}
-
-body, :root, #root, #header, #MComposer, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4,
-._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._3qdh, ._8ca, ._3h8i,
-._6-l ._2us7, ._6-l ._6-p:not([style*="background-image:"]), ._333v, div.sharerSelector, ._529j, ._305j, ._1pph, ._3t_l, ._4pvz,
-._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._24e1, ._-j7,
-._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz,
-._38do, .bo, .cq, ._234-, ._a-5, ._2zh4, ._15ks, ._3oyc, ._36dc, ._3iyw ._3iyx, ._6bes, ._55wo, ._4-dy,
-.tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0,
-.al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._2bdb, ._3ci9,
-._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._6be7,
-._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._5xp2, div.fullwidthMore,
-._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._voz, ._vos,
-._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5_ee, ._3clk,
-._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5,
-._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, ._3tl8, ._65wz, ._4edl,
-.acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._1glm,
-._ue6, ._hdn._hdn, ._6vzw, ._77xj, ._38nq, ._9_7, ._51li,
-._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3bmj, ._5zmb, ._2x2s, ._3kac, ._3kad,
-._3f50, .mentions-placeholder, .mentions, .mentions-measurer, .acg, ._59tu,
-._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._1dbp, ._5zma, ._6beq, ._vi6,
-._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy,
-._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x {
- background: #303030 !important;
-}
-
-._31nf, ._2v9s, ._d4i, article._55wo, ._10c_, ._2jl2, ._6150, ._50mi, ._4-dw, ._4_2z, ._5m_s, ._13fn {
- background: #353535 !important;
-}
-
-.aclb {
- background: rgba(255, 255, 255, 0.2) !important;
-}
-
-._cv_, ._2sq8 {
- background-color: #303030 !important;
-}
-
-#page, ._8l7, ._-j8, ._-j9, ._6o5v, ._uwx, .touch ._uwx.mentions-input {
- background: transparent !important;
-}
-
-.jewel, .flyout, ._52z5, ._13e_, ._5-lw, ._5c0e, .jx-result, ._336p, .mentions-suggest-item, ._2suk,
-.mentions-suggest, ._1xoz, ._1xow {
- background: #303030 !important;
-}
-
-._403n, ._14v5 ._14v8, ._1-kc {
- background: #898989 !important;
-}
-
-button:not([style*=image]):not(.privacyButtons), button::before, .touch ._56bt, ._56be::before, .btnS, .touch::before,
-._590n, ._4g8h, ._2cpp, ._58a0.touched:after,
-.timeline .timelinePublisher, .touched, .sharerAttachment,
-.item a.primary.touched .primarywrap, ._537a, ._7cui,
-._5xo2, ._5u5a::before, ._4u3j, ._15ks, ._5hua, ._59tt, ._41ft, .jx-tokenizer, ._55fj,
-.excessItem, .acr, ._5-lx, ._3g9-, ._55ws, ._6dsj ._3gin, ._69aj,
-._4e8n, ._5pxa._3uj9, ._5n_5, ._u2d, ._56bu::before, ._5h8f, ._d00, ._2066, ._2k51,
-._10sb li.selected, ._2z4j, ._ib-, ._1bhl, ._5a5j, ._6--d, ._77p7,
-._2b06, ._2tsf, ._3gka, .mCount, ._27vc, ._4pv-, ._6pk5,
-._4qax, ._4756, ._w34, ._56bv::before, ._5769, ._34iv, ._z-w, ._t21, .mToken,
-#addMembersTypeahead .mToken.mTokenWeakReference,
-.acbk {
- background: rgba(137, 137, 137, 0.35) !important;
-}
-
-.mQuestionsPollResultsBar .shaded {
- background: #5d86dd !important;
-}
-
-._220g, ._1_y8:after, ._6pk6,
-._2zh4::before, ._2ip_ ._2zh4::before, ._2ip_ ._15kk::before, ._2ip_ ._15kk + ._4u3j::before,
-._58a0:before, ._43mh::before, ._43mh::after, ._1_-1::before, ._1kmv:after, ._1_ac:before {
- background: rgba(255, 255, 255, 0.3) !important;
-}
-
-button ._v89 ._54k8._1fl1 {
- background: #5d86dd !important;
-}
-
-._15kl::before, ._37fd .inlineComposerButton, ._1hb:before,
-._5j35::after, ._2k4b, ._3to7, ._4nw8 {
- border-left: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-._4_d1, ._5cni, ._3jcq {
- border-right: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-._1mx0, ._1rbr, ._5yt8, ._idb, ._cld, ._1e8h, ._z-w, ._1ha, ._1n8h ._1oby, ._5f99, ._2t39,
-._2pbp, ._5rou:first-child, ._egf:first-child, ._io2, ._3qdi ._48_m::after, ._46dd::before,
-._15n_, ._3-2-, ._27ve, ._2s20, ._gui, ._2s21 > *::after, ._32qk, ._d00, ._d01, ._38o9,
-._3u9t, ._55fj, .mEventProfileSection.useBorder td, ._3ils, ._5as0, ._5as2, ._5-lw,
-._52x1, ._3wjp, ._usq, ._2cul:before, ._13e_, .jewel .flyout, ._3bg5 ._52x6, ._56d8, .al {
- border-top: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-._15ny::after, ._z-w, ._8i2, ._2nk0, ._22_8, ._1t4h, ._37fd, ._1ha, ._3bg5 ._56do, ._8he,
-._400s, ._5hoc, ._1bhn, ._5ag6, ._4pvz,
-._301x, ._x08 ._x0a:after, ._36dc, ._6-l ._57jn, ._527k, ._g_k,
-._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, ._2bdc, ._3ci9,
-.mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._38do, ._38dt,
-._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .error, ._35--, ._1wev,
-.jx-result, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._5rgs, ._5xuj, ._1sv1, ._idb,
-._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._5hu6,
-._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc,
-._5pz4, ._5lp4, ._5lp5, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child,
-._5fjw > :first-child {
- border-bottom: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-.item a.primary.touched .primarywrap, ._4dwt ._5y33, ._1ih_, ._5_50, ._6beq, ._69aj,
-._5fjv, ._3on6, ._2u4w, ._2om3, ._2ol-, ._5fjw, ._4z83, ._1gkq, ._4-dy {
- border-top: 1px solid rgba(255, 255, 255, 0.3) !important;
- border-bottom: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-._d4i, ._f6s, .mentions-suggest-item, .mentions-suggest, .sharerAttachment,
-.mToken, #addMembersTypeahead .mToken.mTokenWeakReference, .mQuestionsPollResultsBar,
-._15q7, ._2q7v, ._4dwt ._16ii, ._3qdi::after,
-._2q7w, .acy, ._58ak, ._3t_l, ._4msa, ._3h8i, ._3clk, ._1kt6, ._1ksq,
-._1_y5, ._lr0, ._5hgt, ._2cpp, ._50uu, ._50uw, ._31yd, ._1e3d, ._3xz7, ._1xoz,
-._4kcb, ._2lut, .jewel .touchable-notification.touched, .touchable-notification .touchable.touched,
-.home-notification .touchable.touched, ._6beo ._6ber,
-._73ku ._73jw, ._6--d, ._26vk._56bt,
-._4e8n, ._uww, .mentions-placeholder, .mentions-shadow, .mentions-measurer,
-._5whq, ._59tt, ._41ft::after, .jx-tokenizer, ._3uqf, ._4756, ._1rrd, ._5n_f {
- border: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-.mQuestionsPollResultsBar .shaded, ._1027._13sm {
- border: 1px solid #fff !important;
-}
-
-._3gka {
- border: 1px dashed rgba(255, 255, 255, 0.3) !important;
-}
-
-._4o58::after, .acr, ._t21, ._2bdb,
-.acw, .aclb, ._4qax, ._5h8f {
- border-color: rgba(255, 255, 255, 0.3) !important;
-}
-
-._15ks ._15kl::before {
- border-left: 1px solid transparent !important;
-}
-
-._56bf, .touch .btn {
- border-radius: 0 !important;
- border: 0 !important;
-}
-
-._2cis {
- border-left: 10px solid #303030 !important;
- border-right: 10px solid #303030 !important;
-}
-
-._2cir.selected, ._42rv, ._5zma, ._2x2s {
- border-bottom: 3px solid #fff !important;
-}
-
-._1ss6 {
- border-left: 2px solid #fff !important;
-}
-
-._484w.selected > ._6zf, ._5kqs::after, ._3lvo ._5xum._5xuk, ._x0b {
- border-bottom: 1px solid #fff !important;
-}
-
-._34ee {
- background: rgba(137, 137, 137, 0.35) !important;
- color: #fff !important;
-}
-
-._34em ._34ee {
- background: #5d86dd !important;
- color: #fff !important;
-}
-
-._5as0, ._5cni, ._5as2 {
- background: #303030 !important;
-}
-
-*, *::after, *::before {
- text-shadow: none !important;
- box-shadow: none !important;
-}
-
-[data-sigil=m_login_upsell],
-[data-sigil="m-loading-indicator-animate m-loading-indicator-root"] {
- display: none !important;
-}
-
-::-webkit-input-placeholder {
- color: #fff !important;
-}
-
-:-moz-placeholder {
- color: #fff !important;
-}
-
-::-moz-placeholder {
- color: #fff !important;
-}
-
-:-ms-input-placeholder {
- color: #fff !important;
-}
-
-.excessItem {
- outline: rgba(255, 255, 255, 0.3) !important;
-}
-
-._3m1m {
- background: linear-gradient(transparent, #303030) !important;
-}
-
-@-webkit-keyframes highlightFade {
- 0%, 50% {
- background: rgba(137, 137, 137, 0.35);
- }
- 100% {
- background: #303030;
- }
-}
-@-moz-keyframes highlightFade {
- 0%, 50% {
- background: rgba(137, 137, 137, 0.35);
- }
- 100% {
- background: #303030;
- }
-}
-@keyframes highlightFade {
- 0%, 50% {
- background: rgba(137, 137, 137, 0.35);
- }
- 100% {
- background: #303030;
- }
-}
-@-webkit-keyframes chatHighlightAnimation {
- 0%, 100% {
- background: #303030;
- }
- 50% {
- background: rgba(137, 137, 137, 0.35);
- }
-}
-@-moz-keyframes chatHighlightAnimation {
- 0%, 100% {
- background: #303030;
- }
- 50% {
- background: rgba(137, 137, 137, 0.35);
- }
-}
-@keyframes chatHighlightAnimation {
- 0%, 100% {
- background: #303030;
- }
- 50% {
- background: rgba(137, 137, 137, 0.35);
- }
-}
-._50uu {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 -10 50 50"%3E%3Ccircle cx="25" cy="23" r="3.2"/%3E%3Cpath d="M22 13l-1.83 2H17c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V17c0-1.1-.9-2-2-2h-3.17L28 13h-6zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"/%3E%3Cpath fill="none" d="M13 11h24v24H13z"/%3E%3C/svg%3E') no-repeat !important;
-}
-
-._50uw {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 0 50 50"%3E%3Cpath fill="none" d="M13 26h24v24H13z"/%3E%3Cpath d="M30 31.5V28c0-.55-.45-1-1-1H17c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/%3E%3C/svg%3E') no-repeat !important;
-}
-
-._15km ._15ko::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1.91l-.01-.01L23 10z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15ko._77la::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%235d86dd" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1.91l-.01-.01L23 10z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15kq::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 0 24 24"%3E%3Cpath d="M21.99 4c0-1.1-.89-2-1.99-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4-.01-18z"/%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15kr::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 0 24 24"%3E%3Cpath d="M14 9V5l7 7-7 7v-4.1c-5 0-8.5 1.6-11 5.1 1-5 4-10 11-11z"/%3E%3Cpath fill="none" d="M24 0H0v24h24z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-.story_body_container i.img[data-sigil*=story-popup-context] {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath fill="%23fff" d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
diff --git a/app/src/web/assets/css/themes/material_glass.css b/app/src/web/assets/css/themes/material_glass.css
deleted file mode 100644
index 8e7656b4..00000000
--- a/app/src/web/assets/css/themes/material_glass.css
+++ /dev/null
@@ -1,339 +0,0 @@
-body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5,
-._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk,
-.touched *, ._1_yj, ._1_yl, ._4pj9, ._2bdc, ._3qdh ._3qdn ._3qdk, ._3qdk ._48_q,
-._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, .cq, ._usr,
-._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5,
-._1lf4, ._1hiz, ._xod, ._5ag5, ._zmk, ._3t_h, ._5lm6, ._3clv, ._3zlc, ._36rd,
-._31zk, ._31zl, ._3xsa, ._3xs9, ._2-4s, ._2fzz ul, ._3z10, ._4mo, ._2om6,
-._43mh, .touch .btn, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35,
-._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu,
-._18qg, ._1_ac, ._529p, ._4dwt ._1vh3, ._4a5f, ._23_t, ._2rzc, ._23_s, ._2rzd,
-._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._36e0, ._4-dy,
-._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, .mCount, ._27vc, ._24e1, ._2rbw, ._3iyw ._3mzw,
-textarea:not([style*="color: rgb"]), ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782,
-._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._6--d, ._77p7, ._7h_g,
-._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._38d-, ._3n8y, ._38dt, ._3oyg, ._21dc,
-._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._6xqt, ._7cui,
-._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._7cdj,
-._2new, .appCenterCategorySelectorButton, ._1ksq, ._1kt6, ._6ber, ._mxb, ._3oyd, ._3gir, ._3gis,
-div.sharerSelector, .footer, ._4pv_, ._1dbp, ._3kad, ._20zc, ._2i5v, ._2i5w,
-a, ._5fpq, ._4gux, ._3bg5 ._52x1, ._3bg5 ._52x2, ._6dsj ._3gin, ._hdn._hdn,
-.mentions-input:not([style*="color: rgb"]), .mentions-placeholder:not([style*="color: rgb"]),
-.largeStatusBox .placeHolder, .fcw, ._2rgt, ._67i4 ._5hu6 ._59tt,
-._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4,
-._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs,
-h1, h2, h3, h4, h5, h6 {
- color: #fff !important;
-}
-
-strong > a, ._15ks ._2q8z._2q8z, ._1e3e, .blueName, ._5kqs ._55sr {
- color: #5d86dd !important;
-}
-
-._42nf ._42ng {
- color: transparent !important;
-}
-
-p > a, .msg span > a {
- color: #5d86dd !important;
-}
-
-#viewport {
- background: rgba(0, 0, 0, 0.1) !important;
-}
-
-body, :root, #root, #header, #MComposer, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4,
-._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._3qdh, ._8ca, ._3h8i,
-._6-l ._2us7, ._6-l ._6-p:not([style*="background-image:"]), ._333v, div.sharerSelector, ._529j, ._305j, ._1pph, ._3t_l, ._4pvz,
-._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._24e1, ._-j7,
-._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz,
-._38do, .bo, .cq, ._234-, ._a-5, ._2zh4, ._15ks, ._3oyc, ._36dc, ._3iyw ._3iyx, ._6bes, ._55wo, ._4-dy,
-.tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0,
-.al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._2bdb, ._3ci9,
-._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._6be7,
-._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._5xp2, div.fullwidthMore,
-._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._voz, ._vos,
-._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5_ee, ._3clk,
-._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5,
-._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, ._3tl8, ._65wz, ._4edl,
-.acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._1glm,
-._ue6, ._hdn._hdn, ._6vzw, ._77xj, ._38nq, ._9_7, ._51li,
-._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3bmj, ._5zmb, ._2x2s, ._3kac, ._3kad,
-._3f50, .mentions-placeholder, .mentions, .mentions-measurer, .acg, ._59tu,
-._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._1dbp, ._5zma, ._6beq, ._vi6,
-._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy,
-._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x {
- background: transparent !important;
-}
-
-._31nf, ._2v9s, ._d4i, article._55wo, ._10c_, ._2jl2, ._6150, ._50mi, ._4-dw, ._4_2z, ._5m_s, ._13fn {
- background: rgba(0, 0, 0, 0.25) !important;
-}
-
-.aclb {
- background: rgba(255, 255, 255, 0.15) !important;
-}
-
-._cv_, ._2sq8 {
- background-color: transparent !important;
-}
-
-#page, ._8l7, ._-j8, ._-j9, ._6o5v, ._uwx, .touch ._uwx.mentions-input {
- background: transparent !important;
-}
-
-.jewel, .flyout, ._52z5, ._13e_, ._5-lw, ._5c0e, .jx-result, ._336p, .mentions-suggest-item, ._2suk,
-.mentions-suggest, ._1xoz, ._1xow {
- background: black !important;
-}
-
-._403n, ._14v5 ._14v8, ._1-kc {
- background: #595959 !important;
-}
-
-button:not([style*=image]):not(.privacyButtons), button::before, .touch ._56bt, ._56be::before, .btnS, .touch::before,
-._590n, ._4g8h, ._2cpp, ._58a0.touched:after,
-.timeline .timelinePublisher, .touched, .sharerAttachment,
-.item a.primary.touched .primarywrap, ._537a, ._7cui,
-._5xo2, ._5u5a::before, ._4u3j, ._15ks, ._5hua, ._59tt, ._41ft, .jx-tokenizer, ._55fj,
-.excessItem, .acr, ._5-lx, ._3g9-, ._55ws, ._6dsj ._3gin, ._69aj,
-._4e8n, ._5pxa._3uj9, ._5n_5, ._u2d, ._56bu::before, ._5h8f, ._d00, ._2066, ._2k51,
-._10sb li.selected, ._2z4j, ._ib-, ._1bhl, ._5a5j, ._6--d, ._77p7,
-._2b06, ._2tsf, ._3gka, .mCount, ._27vc, ._4pv-, ._6pk5,
-._4qax, ._4756, ._w34, ._56bv::before, ._5769, ._34iv, ._z-w, ._t21, .mToken,
-#addMembersTypeahead .mToken.mTokenWeakReference,
-.acbk {
- background: rgba(89, 89, 89, 0.35) !important;
-}
-
-.mQuestionsPollResultsBar .shaded {
- background: #5d86dd !important;
-}
-
-._220g, ._1_y8:after, ._6pk6,
-._2zh4::before, ._2ip_ ._2zh4::before, ._2ip_ ._15kk::before, ._2ip_ ._15kk + ._4u3j::before,
-._58a0:before, ._43mh::before, ._43mh::after, ._1_-1::before, ._1kmv:after, ._1_ac:before {
- background: rgba(255, 255, 255, 0.3) !important;
-}
-
-button ._v89 ._54k8._1fl1 {
- background: #5d86dd !important;
-}
-
-._15kl::before, ._37fd .inlineComposerButton, ._1hb:before,
-._5j35::after, ._2k4b, ._3to7, ._4nw8 {
- border-left: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-._4_d1, ._5cni, ._3jcq {
- border-right: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-._1mx0, ._1rbr, ._5yt8, ._idb, ._cld, ._1e8h, ._z-w, ._1ha, ._1n8h ._1oby, ._5f99, ._2t39,
-._2pbp, ._5rou:first-child, ._egf:first-child, ._io2, ._3qdi ._48_m::after, ._46dd::before,
-._15n_, ._3-2-, ._27ve, ._2s20, ._gui, ._2s21 > *::after, ._32qk, ._d00, ._d01, ._38o9,
-._3u9t, ._55fj, .mEventProfileSection.useBorder td, ._3ils, ._5as0, ._5as2, ._5-lw,
-._52x1, ._3wjp, ._usq, ._2cul:before, ._13e_, .jewel .flyout, ._3bg5 ._52x6, ._56d8, .al {
- border-top: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-._15ny::after, ._z-w, ._8i2, ._2nk0, ._22_8, ._1t4h, ._37fd, ._1ha, ._3bg5 ._56do, ._8he,
-._400s, ._5hoc, ._1bhn, ._5ag6, ._4pvz,
-._301x, ._x08 ._x0a:after, ._36dc, ._6-l ._57jn, ._527k, ._g_k,
-._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, ._2bdc, ._3ci9,
-.mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._38do, ._38dt,
-._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .error, ._35--, ._1wev,
-.jx-result, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._5rgs, ._5xuj, ._1sv1, ._idb,
-._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._5hu6,
-._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc,
-._5pz4, ._5lp4, ._5lp5, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child,
-._5fjw > :first-child {
- border-bottom: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-.item a.primary.touched .primarywrap, ._4dwt ._5y33, ._1ih_, ._5_50, ._6beq, ._69aj,
-._5fjv, ._3on6, ._2u4w, ._2om3, ._2ol-, ._5fjw, ._4z83, ._1gkq, ._4-dy {
- border-top: 1px solid rgba(255, 255, 255, 0.3) !important;
- border-bottom: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-._d4i, ._f6s, .mentions-suggest-item, .mentions-suggest, .sharerAttachment,
-.mToken, #addMembersTypeahead .mToken.mTokenWeakReference, .mQuestionsPollResultsBar,
-._15q7, ._2q7v, ._4dwt ._16ii, ._3qdi::after,
-._2q7w, .acy, ._58ak, ._3t_l, ._4msa, ._3h8i, ._3clk, ._1kt6, ._1ksq,
-._1_y5, ._lr0, ._5hgt, ._2cpp, ._50uu, ._50uw, ._31yd, ._1e3d, ._3xz7, ._1xoz,
-._4kcb, ._2lut, .jewel .touchable-notification.touched, .touchable-notification .touchable.touched,
-.home-notification .touchable.touched, ._6beo ._6ber,
-._73ku ._73jw, ._6--d, ._26vk._56bt,
-._4e8n, ._uww, .mentions-placeholder, .mentions-shadow, .mentions-measurer,
-._5whq, ._59tt, ._41ft::after, .jx-tokenizer, ._3uqf, ._4756, ._1rrd, ._5n_f {
- border: 1px solid rgba(255, 255, 255, 0.3) !important;
-}
-
-.mQuestionsPollResultsBar .shaded, ._1027._13sm {
- border: 1px solid #fff !important;
-}
-
-._3gka {
- border: 1px dashed rgba(255, 255, 255, 0.3) !important;
-}
-
-._4o58::after, .acr, ._t21, ._2bdb,
-.acw, .aclb, ._4qax, ._5h8f {
- border-color: rgba(255, 255, 255, 0.3) !important;
-}
-
-._15ks ._15kl::before {
- border-left: 1px solid transparent !important;
-}
-
-._56bf, .touch .btn {
- border-radius: 0 !important;
- border: 0 !important;
-}
-
-._2cis {
- border-left: 10px solid transparent !important;
- border-right: 10px solid transparent !important;
-}
-
-._2cir.selected, ._42rv, ._5zma, ._2x2s {
- border-bottom: 3px solid #fff !important;
-}
-
-._1ss6 {
- border-left: 2px solid #fff !important;
-}
-
-._484w.selected > ._6zf, ._5kqs::after, ._3lvo ._5xum._5xuk, ._x0b {
- border-bottom: 1px solid #fff !important;
-}
-
-._34ee {
- background: rgba(89, 89, 89, 0.35) !important;
- color: #fff !important;
-}
-
-._34em ._34ee {
- background: #5d86dd !important;
- color: #fff !important;
-}
-
-._5as0, ._5cni, ._5as2 {
- background: black !important;
-}
-
-*, *::after, *::before {
- text-shadow: none !important;
- box-shadow: none !important;
-}
-
-[data-sigil=m_login_upsell],
-[data-sigil="m-loading-indicator-animate m-loading-indicator-root"] {
- display: none !important;
-}
-
-::-webkit-input-placeholder {
- color: #fff !important;
-}
-
-:-moz-placeholder {
- color: #fff !important;
-}
-
-::-moz-placeholder {
- color: #fff !important;
-}
-
-:-ms-input-placeholder {
- color: #fff !important;
-}
-
-.excessItem {
- outline: rgba(255, 255, 255, 0.3) !important;
-}
-
-._3m1m {
- background: linear-gradient(transparent, black) !important;
-}
-
-@-webkit-keyframes highlightFade {
- 0%, 50% {
- background: rgba(89, 89, 89, 0.35);
- }
- 100% {
- background: transparent;
- }
-}
-@-moz-keyframes highlightFade {
- 0%, 50% {
- background: rgba(89, 89, 89, 0.35);
- }
- 100% {
- background: transparent;
- }
-}
-@keyframes highlightFade {
- 0%, 50% {
- background: rgba(89, 89, 89, 0.35);
- }
- 100% {
- background: transparent;
- }
-}
-@-webkit-keyframes chatHighlightAnimation {
- 0%, 100% {
- background: transparent;
- }
- 50% {
- background: rgba(89, 89, 89, 0.35);
- }
-}
-@-moz-keyframes chatHighlightAnimation {
- 0%, 100% {
- background: transparent;
- }
- 50% {
- background: rgba(89, 89, 89, 0.35);
- }
-}
-@keyframes chatHighlightAnimation {
- 0%, 100% {
- background: transparent;
- }
- 50% {
- background: rgba(89, 89, 89, 0.35);
- }
-}
-._50uu {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 -10 50 50"%3E%3Ccircle cx="25" cy="23" r="3.2"/%3E%3Cpath d="M22 13l-1.83 2H17c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V17c0-1.1-.9-2-2-2h-3.17L28 13h-6zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"/%3E%3Cpath fill="none" d="M13 11h24v24H13z"/%3E%3C/svg%3E') no-repeat !important;
-}
-
-._50uw {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 0 50 50"%3E%3Cpath fill="none" d="M13 26h24v24H13z"/%3E%3Cpath d="M30 31.5V28c0-.55-.45-1-1-1H17c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/%3E%3C/svg%3E') no-repeat !important;
-}
-
-._15km ._15ko::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1.91l-.01-.01L23 10z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15ko._77la::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%235d86dd" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1.91l-.01-.01L23 10z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15kq::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 0 24 24"%3E%3Cpath d="M21.99 4c0-1.1-.89-2-1.99-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4-.01-18z"/%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15kr::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23fff" viewBox="0 0 24 24"%3E%3Cpath d="M14 9V5l7 7-7 7v-4.1c-5 0-8.5 1.6-11 5.1 1-5 4-10 11-11z"/%3E%3Cpath fill="none" d="M24 0H0v24h24z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-.story_body_container i.img[data-sigil*=story-popup-context] {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath fill="%23fff" d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
diff --git a/app/src/web/assets/css/themes/material_light.css b/app/src/web/assets/css/themes/material_light.css
deleted file mode 100644
index fb738862..00000000
--- a/app/src/web/assets/css/themes/material_light.css
+++ /dev/null
@@ -1,339 +0,0 @@
-body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5,
-._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk,
-.touched *, ._1_yj, ._1_yl, ._4pj9, ._2bdc, ._3qdh ._3qdn ._3qdk, ._3qdk ._48_q,
-._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, .cq, ._usr,
-._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5,
-._1lf4, ._1hiz, ._xod, ._5ag5, ._zmk, ._3t_h, ._5lm6, ._3clv, ._3zlc, ._36rd,
-._31zk, ._31zl, ._3xsa, ._3xs9, ._2-4s, ._2fzz ul, ._3z10, ._4mo, ._2om6,
-._43mh, .touch .btn, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35,
-._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu,
-._18qg, ._1_ac, ._529p, ._4dwt ._1vh3, ._4a5f, ._23_t, ._2rzc, ._23_s, ._2rzd,
-._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._36e0, ._4-dy,
-._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, .mCount, ._27vc, ._24e1, ._2rbw, ._3iyw ._3mzw,
-textarea:not([style*="color: rgb"]), ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782,
-._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._6--d, ._77p7, ._7h_g,
-._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._38d-, ._3n8y, ._38dt, ._3oyg, ._21dc,
-._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._6xqt, ._7cui,
-._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._7cdj,
-._2new, .appCenterCategorySelectorButton, ._1ksq, ._1kt6, ._6ber, ._mxb, ._3oyd, ._3gir, ._3gis,
-div.sharerSelector, .footer, ._4pv_, ._1dbp, ._3kad, ._20zc, ._2i5v, ._2i5w,
-a, ._5fpq, ._4gux, ._3bg5 ._52x1, ._3bg5 ._52x2, ._6dsj ._3gin, ._hdn._hdn,
-.mentions-input:not([style*="color: rgb"]), .mentions-placeholder:not([style*="color: rgb"]),
-.largeStatusBox .placeHolder, .fcw, ._2rgt, ._67i4 ._5hu6 ._59tt,
-._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4,
-._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs,
-h1, h2, h3, h4, h5, h6 {
- color: #000 !important;
-}
-
-strong > a, ._15ks ._2q8z._2q8z, ._1e3e, .blueName, ._5kqs ._55sr {
- color: #3b5998 !important;
-}
-
-._42nf ._42ng {
- color: transparent !important;
-}
-
-p > a, .msg span > a {
- color: #3b5998 !important;
-}
-
-#viewport {
- background: #fafafa !important;
-}
-
-body, :root, #root, #header, #MComposer, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4,
-._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._3qdh, ._8ca, ._3h8i,
-._6-l ._2us7, ._6-l ._6-p:not([style*="background-image:"]), ._333v, div.sharerSelector, ._529j, ._305j, ._1pph, ._3t_l, ._4pvz,
-._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._24e1, ._-j7,
-._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz,
-._38do, .bo, .cq, ._234-, ._a-5, ._2zh4, ._15ks, ._3oyc, ._36dc, ._3iyw ._3iyx, ._6bes, ._55wo, ._4-dy,
-.tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0,
-.al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._2bdb, ._3ci9,
-._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._6be7,
-._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._5xp2, div.fullwidthMore,
-._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._voz, ._vos,
-._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5_ee, ._3clk,
-._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5,
-._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, ._3tl8, ._65wz, ._4edl,
-.acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._1glm,
-._ue6, ._hdn._hdn, ._6vzw, ._77xj, ._38nq, ._9_7, ._51li,
-._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3bmj, ._5zmb, ._2x2s, ._3kac, ._3kad,
-._3f50, .mentions-placeholder, .mentions, .mentions-measurer, .acg, ._59tu,
-._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._1dbp, ._5zma, ._6beq, ._vi6,
-._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy,
-._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x {
- background: #fafafa !important;
-}
-
-._31nf, ._2v9s, ._d4i, article._55wo, ._10c_, ._2jl2, ._6150, ._50mi, ._4-dw, ._4_2z, ._5m_s, ._13fn {
- background: #fff !important;
-}
-
-.aclb {
- background: #ddd !important;
-}
-
-._cv_, ._2sq8 {
- background-color: #fafafa !important;
-}
-
-#page, ._8l7, ._-j8, ._-j9, ._6o5v, ._uwx, .touch ._uwx.mentions-input {
- background: transparent !important;
-}
-
-.jewel, .flyout, ._52z5, ._13e_, ._5-lw, ._5c0e, .jx-result, ._336p, .mentions-suggest-item, ._2suk,
-.mentions-suggest, ._1xoz, ._1xow {
- background: #fafafa !important;
-}
-
-._403n, ._14v5 ._14v8, ._1-kc {
- background: #e6e6e6 !important;
-}
-
-button:not([style*=image]):not(.privacyButtons), button::before, .touch ._56bt, ._56be::before, .btnS, .touch::before,
-._590n, ._4g8h, ._2cpp, ._58a0.touched:after,
-.timeline .timelinePublisher, .touched, .sharerAttachment,
-.item a.primary.touched .primarywrap, ._537a, ._7cui,
-._5xo2, ._5u5a::before, ._4u3j, ._15ks, ._5hua, ._59tt, ._41ft, .jx-tokenizer, ._55fj,
-.excessItem, .acr, ._5-lx, ._3g9-, ._55ws, ._6dsj ._3gin, ._69aj,
-._4e8n, ._5pxa._3uj9, ._5n_5, ._u2d, ._56bu::before, ._5h8f, ._d00, ._2066, ._2k51,
-._10sb li.selected, ._2z4j, ._ib-, ._1bhl, ._5a5j, ._6--d, ._77p7,
-._2b06, ._2tsf, ._3gka, .mCount, ._27vc, ._4pv-, ._6pk5,
-._4qax, ._4756, ._w34, ._56bv::before, ._5769, ._34iv, ._z-w, ._t21, .mToken,
-#addMembersTypeahead .mToken.mTokenWeakReference,
-.acbk {
- background: rgba(230, 230, 230, 0.35) !important;
-}
-
-.mQuestionsPollResultsBar .shaded {
- background: #3b5998 !important;
-}
-
-._220g, ._1_y8:after, ._6pk6,
-._2zh4::before, ._2ip_ ._2zh4::before, ._2ip_ ._15kk::before, ._2ip_ ._15kk + ._4u3j::before,
-._58a0:before, ._43mh::before, ._43mh::after, ._1_-1::before, ._1kmv:after, ._1_ac:before {
- background: rgba(0, 0, 0, 0.3) !important;
-}
-
-button ._v89 ._54k8._1fl1 {
- background: #3b5998 !important;
-}
-
-._15kl::before, ._37fd .inlineComposerButton, ._1hb:before,
-._5j35::after, ._2k4b, ._3to7, ._4nw8 {
- border-left: 1px solid rgba(0, 0, 0, 0.3) !important;
-}
-
-._4_d1, ._5cni, ._3jcq {
- border-right: 1px solid rgba(0, 0, 0, 0.3) !important;
-}
-
-._1mx0, ._1rbr, ._5yt8, ._idb, ._cld, ._1e8h, ._z-w, ._1ha, ._1n8h ._1oby, ._5f99, ._2t39,
-._2pbp, ._5rou:first-child, ._egf:first-child, ._io2, ._3qdi ._48_m::after, ._46dd::before,
-._15n_, ._3-2-, ._27ve, ._2s20, ._gui, ._2s21 > *::after, ._32qk, ._d00, ._d01, ._38o9,
-._3u9t, ._55fj, .mEventProfileSection.useBorder td, ._3ils, ._5as0, ._5as2, ._5-lw,
-._52x1, ._3wjp, ._usq, ._2cul:before, ._13e_, .jewel .flyout, ._3bg5 ._52x6, ._56d8, .al {
- border-top: 1px solid rgba(0, 0, 0, 0.3) !important;
-}
-
-._15ny::after, ._z-w, ._8i2, ._2nk0, ._22_8, ._1t4h, ._37fd, ._1ha, ._3bg5 ._56do, ._8he,
-._400s, ._5hoc, ._1bhn, ._5ag6, ._4pvz,
-._301x, ._x08 ._x0a:after, ._36dc, ._6-l ._57jn, ._527k, ._g_k,
-._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, ._2bdc, ._3ci9,
-.mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._38do, ._38dt,
-._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .error, ._35--, ._1wev,
-.jx-result, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._5rgs, ._5xuj, ._1sv1, ._idb,
-._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._5hu6,
-._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc,
-._5pz4, ._5lp4, ._5lp5, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child,
-._5fjw > :first-child {
- border-bottom: 1px solid rgba(0, 0, 0, 0.3) !important;
-}
-
-.item a.primary.touched .primarywrap, ._4dwt ._5y33, ._1ih_, ._5_50, ._6beq, ._69aj,
-._5fjv, ._3on6, ._2u4w, ._2om3, ._2ol-, ._5fjw, ._4z83, ._1gkq, ._4-dy {
- border-top: 1px solid rgba(0, 0, 0, 0.3) !important;
- border-bottom: 1px solid rgba(0, 0, 0, 0.3) !important;
-}
-
-._d4i, ._f6s, .mentions-suggest-item, .mentions-suggest, .sharerAttachment,
-.mToken, #addMembersTypeahead .mToken.mTokenWeakReference, .mQuestionsPollResultsBar,
-._15q7, ._2q7v, ._4dwt ._16ii, ._3qdi::after,
-._2q7w, .acy, ._58ak, ._3t_l, ._4msa, ._3h8i, ._3clk, ._1kt6, ._1ksq,
-._1_y5, ._lr0, ._5hgt, ._2cpp, ._50uu, ._50uw, ._31yd, ._1e3d, ._3xz7, ._1xoz,
-._4kcb, ._2lut, .jewel .touchable-notification.touched, .touchable-notification .touchable.touched,
-.home-notification .touchable.touched, ._6beo ._6ber,
-._73ku ._73jw, ._6--d, ._26vk._56bt,
-._4e8n, ._uww, .mentions-placeholder, .mentions-shadow, .mentions-measurer,
-._5whq, ._59tt, ._41ft::after, .jx-tokenizer, ._3uqf, ._4756, ._1rrd, ._5n_f {
- border: 1px solid rgba(0, 0, 0, 0.3) !important;
-}
-
-.mQuestionsPollResultsBar .shaded, ._1027._13sm {
- border: 1px solid #000 !important;
-}
-
-._3gka {
- border: 1px dashed rgba(0, 0, 0, 0.3) !important;
-}
-
-._4o58::after, .acr, ._t21, ._2bdb,
-.acw, .aclb, ._4qax, ._5h8f {
- border-color: rgba(0, 0, 0, 0.3) !important;
-}
-
-._15ks ._15kl::before {
- border-left: 1px solid transparent !important;
-}
-
-._56bf, .touch .btn {
- border-radius: 0 !important;
- border: 0 !important;
-}
-
-._2cis {
- border-left: 10px solid #fafafa !important;
- border-right: 10px solid #fafafa !important;
-}
-
-._2cir.selected, ._42rv, ._5zma, ._2x2s {
- border-bottom: 3px solid #000 !important;
-}
-
-._1ss6 {
- border-left: 2px solid #000 !important;
-}
-
-._484w.selected > ._6zf, ._5kqs::after, ._3lvo ._5xum._5xuk, ._x0b {
- border-bottom: 1px solid #000 !important;
-}
-
-._34ee {
- background: rgba(230, 230, 230, 0.35) !important;
- color: #000 !important;
-}
-
-._34em ._34ee {
- background: #3b5998 !important;
- color: #fff !important;
-}
-
-._5as0, ._5cni, ._5as2 {
- background: #fafafa !important;
-}
-
-*, *::after, *::before {
- text-shadow: none !important;
- box-shadow: none !important;
-}
-
-[data-sigil=m_login_upsell],
-[data-sigil="m-loading-indicator-animate m-loading-indicator-root"] {
- display: none !important;
-}
-
-::-webkit-input-placeholder {
- color: #000 !important;
-}
-
-:-moz-placeholder {
- color: #000 !important;
-}
-
-::-moz-placeholder {
- color: #000 !important;
-}
-
-:-ms-input-placeholder {
- color: #000 !important;
-}
-
-.excessItem {
- outline: rgba(0, 0, 0, 0.3) !important;
-}
-
-._3m1m {
- background: linear-gradient(transparent, #fafafa) !important;
-}
-
-@-webkit-keyframes highlightFade {
- 0%, 50% {
- background: rgba(230, 230, 230, 0.35);
- }
- 100% {
- background: #fafafa;
- }
-}
-@-moz-keyframes highlightFade {
- 0%, 50% {
- background: rgba(230, 230, 230, 0.35);
- }
- 100% {
- background: #fafafa;
- }
-}
-@keyframes highlightFade {
- 0%, 50% {
- background: rgba(230, 230, 230, 0.35);
- }
- 100% {
- background: #fafafa;
- }
-}
-@-webkit-keyframes chatHighlightAnimation {
- 0%, 100% {
- background: #fafafa;
- }
- 50% {
- background: rgba(230, 230, 230, 0.35);
- }
-}
-@-moz-keyframes chatHighlightAnimation {
- 0%, 100% {
- background: #fafafa;
- }
- 50% {
- background: rgba(230, 230, 230, 0.35);
- }
-}
-@keyframes chatHighlightAnimation {
- 0%, 100% {
- background: #fafafa;
- }
- 50% {
- background: rgba(230, 230, 230, 0.35);
- }
-}
-._50uu {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23000" viewBox="0 -10 50 50"%3E%3Ccircle cx="25" cy="23" r="3.2"/%3E%3Cpath d="M22 13l-1.83 2H17c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V17c0-1.1-.9-2-2-2h-3.17L28 13h-6zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"/%3E%3Cpath fill="none" d="M13 11h24v24H13z"/%3E%3C/svg%3E') no-repeat !important;
-}
-
-._50uw {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23000" viewBox="0 0 50 50"%3E%3Cpath fill="none" d="M13 26h24v24H13z"/%3E%3Cpath d="M30 31.5V28c0-.55-.45-1-1-1H17c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/%3E%3C/svg%3E') no-repeat !important;
-}
-
-._15km ._15ko::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23000" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1.91l-.01-.01L23 10z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15ko._77la::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%233b5998" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1.91l-.01-.01L23 10z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15kq::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23000" viewBox="0 0 24 24"%3E%3Cpath d="M21.99 4c0-1.1-.89-2-1.99-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4-.01-18z"/%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-._15km ._15kr::before {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23000" viewBox="0 0 24 24"%3E%3Cpath d="M14 9V5l7 7-7 7v-4.1c-5 0-8.5 1.6-11 5.1 1-5 4-10 11-11z"/%3E%3Cpath fill="none" d="M24 0H0v24h24z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
-
-.story_body_container i.img[data-sigil*=story-popup-context] {
- background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"%3E%3Cpath fill="none" d="M0 0h24v24H0z"/%3E%3Cpath fill="%23000" d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/%3E%3C/svg%3E') no-repeat !important;
- background-position: center !important;
-}
diff --git a/app/src/web/assets/js/click_a.js b/app/src/web/assets/js/click_a.js
deleted file mode 100644
index be69bb8c..00000000
--- a/app/src/web/assets/js/click_a.js
+++ /dev/null
@@ -1,46 +0,0 @@
-"use strict";
-(function () {
- var prevented = false;
- var _frostAClick = function (e) {
- var target = e.target || e.currentTarget || e.srcElement;
- if (!(target instanceof Element)) {
- console.log("No element found");
- return;
- }
- var element = target;
- for (var i = 0; i < 2; i++) {
- if (element.tagName !== 'A') {
- element = element.parentElement;
- }
- }
- if (element.tagName === 'A') {
- if (!prevented) {
- var url = element.getAttribute('href');
- if (!url || url === '#') {
- return;
- }
- console.log("Click intercept " + url);
- if (Frost.loadUrl(url)) {
- e.stopPropagation();
- e.preventDefault();
- }
- }
- else {
- console.log("Click intercept prevented");
- }
- }
- };
- var _frostPreventClick = function () {
- console.log("Click _frostPrevented");
- prevented = true;
- };
- document.addEventListener('click', _frostAClick, true);
- var clickTimeout = undefined;
- document.addEventListener('touchstart', function () {
- clickTimeout = setTimeout(_frostPreventClick, 400);
- }, true);
- document.addEventListener('touchend', function () {
- prevented = false;
- clearTimeout(clickTimeout);
- }, true);
-}).call(undefined);
diff --git a/app/src/web/assets/js/click_debugger.js b/app/src/web/assets/js/click_debugger.js
deleted file mode 100644
index 16729899..00000000
--- a/app/src/web/assets/js/click_debugger.js
+++ /dev/null
@@ -1,12 +0,0 @@
-"use strict";
-(function () {
- var _frostAContext = function (e) {
- var element = e.target || e.currentTarget || e.srcElement;
- if (!(element instanceof Element)) {
- console.log("No element found");
- return;
- }
- console.log("Clicked element " + element.tagName + " " + element.className);
- };
- document.addEventListener('contextmenu', _frostAContext, true);
-}).call(undefined);
diff --git a/app/src/web/assets/js/context_a.js b/app/src/web/assets/js/context_a.js
deleted file mode 100644
index 61192b28..00000000
--- a/app/src/web/assets/js/context_a.js
+++ /dev/null
@@ -1,98 +0,0 @@
-"use strict";
-(function () {
- var longClick = false;
- var _frostCopyComment = function (e, target) {
- if (!target.hasAttribute('data-commentid')) {
- return false;
- }
- var text = target.innerText;
- console.log("Copy comment " + text);
- Frost.contextMenu(null, text);
- return true;
- };
- var _frostCopyPost = function (e, target) {
- if (target.tagName !== 'A') {
- return false;
- }
- var parent1 = target.parentElement;
- if (!parent1 || parent1.tagName !== 'DIV') {
- return false;
- }
- var parent2 = parent1.parentElement;
- if (!parent2 || !parent2.classList.contains('story_body_container')) {
- return false;
- }
- var url = target.getAttribute('href');
- var text = parent1.innerText;
- console.log("Copy post " + url + " " + text);
- Frost.contextMenu(url, text);
- return true;
- };
- var _getImageStyleUrl = function (el) {
- var img = el.querySelector("[style*=\"background-image: url(\"]");
- if (!img) {
- return null;
- }
- return window.getComputedStyle(img, null).backgroundImage.trim().slice(4, -1);
- };
- var _frostImage = function (e, target) {
- var element = target;
- for (var i = 0; i < 2; i++) {
- if (element.tagName !== 'A') {
- element = element.parentElement;
- }
- else {
- break;
- }
- }
- if (element.tagName !== 'A') {
- return false;
- }
- var url = element.getAttribute('href');
- if (!url || url === '#') {
- return false;
- }
- var text = element.parentElement.innerText;
- var imageUrl = _getImageStyleUrl(element) || _getImageStyleUrl(element.parentElement);
- if (imageUrl) {
- console.log("Context image: " + imageUrl);
- Frost.loadImage(imageUrl, text);
- return true;
- }
- var img = element.querySelector("img[src*=scontent]");
- if (img instanceof HTMLMediaElement) {
- var imgUrl = img.src;
- console.log("Context img: " + imgUrl);
- Frost.loadImage(imgUrl, text);
- return true;
- }
- console.log("Context content " + url + " " + text);
- Frost.contextMenu(url, text);
- return true;
- };
- var handlers = [_frostImage, _frostCopyComment, _frostCopyPost];
- var _frostAContext = function (e) {
- Frost.longClick(true);
- longClick = true;
- var target = e.target || e.currentTarget || e.srcElement;
- if (!(target instanceof HTMLElement)) {
- console.log("No element found");
- return;
- }
- for (var _i = 0, handlers_1 = handlers; _i < handlers_1.length; _i++) {
- var h = handlers_1[_i];
- if (h(e, target)) {
- e.stopPropagation();
- e.preventDefault();
- return;
- }
- }
- };
- document.addEventListener('contextmenu', _frostAContext, true);
- document.addEventListener('touchend', function () {
- if (longClick) {
- Frost.longClick(false);
- longClick = false;
- }
- }, true);
-}).call(undefined);
diff --git a/app/src/web/assets/js/document_watcher.js b/app/src/web/assets/js/document_watcher.js
deleted file mode 100644
index 12252201..00000000
--- a/app/src/web/assets/js/document_watcher.js
+++ /dev/null
@@ -1,23 +0,0 @@
-"use strict";
-(function () {
- var isReady = function () {
- return document.body.scrollHeight > innerHeight + 100;
- };
- if (isReady()) {
- console.log('Already ready');
- Frost.isReady();
- return;
- }
- console.log('Injected document watcher');
- var observer = new MutationObserver(function () {
- if (isReady()) {
- observer.disconnect();
- Frost.isReady();
- console.log("Documented surpassed height in " + performance.now());
- }
- });
- observer.observe(document, {
- childList: true,
- subtree: true
- });
-}).call(undefined);
diff --git a/app/src/web/assets/js/header_badges.js b/app/src/web/assets/js/header_badges.js
deleted file mode 100644
index b1ceee05..00000000
--- a/app/src/web/assets/js/header_badges.js
+++ /dev/null
@@ -1,7 +0,0 @@
-"use strict";
-(function () {
- var header = document.getElementById('mJewelNav');
- if (header) {
- Frost.handleHeader(header.outerHTML);
- }
-}).call(undefined);
diff --git a/app/src/web/assets/js/media.js b/app/src/web/assets/js/media.js
deleted file mode 100644
index baeba0a1..00000000
--- a/app/src/web/assets/js/media.js
+++ /dev/null
@@ -1,41 +0,0 @@
-"use strict";
-(function () {
- var _frostMediaClick = function (e) {
- var target = e.target || e.srcElement;
- if (!(target instanceof HTMLElement)) {
- return;
- }
- var element = target;
- var dataset = element.dataset;
- if (!dataset || !dataset.sigil || dataset.sigil.toLowerCase().indexOf('inlinevideo') == -1) {
- return;
- }
- var i = 0;
- while (!element.hasAttribute('data-store')) {
- if (++i > 2) {
- return;
- }
- element = element.parentNode;
- }
- var store = element.dataset.store;
- if (!store) {
- return;
- }
- var dataStore;
- try {
- dataStore = JSON.parse(store);
- }
- catch (e) {
- return;
- }
- var url = dataStore.src;
- if (!url || url.lastIndexOf('http', 0) !== 0) {
- return;
- }
- console.log("Inline video " + url);
- if (Frost.loadVideo(url, dataStore.animatedGifVideo || false)) {
- e.stopPropagation();
- }
- };
- document.addEventListener('click', _frostMediaClick, true);
-}).call(undefined);
diff --git a/app/src/web/assets/js/menu.js b/app/src/web/assets/js/menu.js
deleted file mode 100644
index b6a30209..00000000
--- a/app/src/web/assets/js/menu.js
+++ /dev/null
@@ -1,55 +0,0 @@
-"use strict";
-(function () {
- var viewport = document.querySelector("#viewport");
- var root = document.querySelector("#root");
- var bookmarkJewel = document.querySelector("#bookmarks_jewel");
- if (!viewport || !root || !bookmarkJewel) {
- console.log('Menu.js: main elements not found');
- Frost.emit(0);
- return;
- }
- var menuA = bookmarkJewel.querySelector("a");
- if (!menuA) {
- console.log('Menu.js: menu links not found');
- Frost.emit(0);
- return;
- }
- var jewel = document.querySelector('#mJewelNav');
- if (!jewel) {
- console.log('Menu.js: jewel is null');
- return;
- }
- var y = new MutationObserver(function () {
- viewport.removeAttribute('style');
- root.removeAttribute('style');
- });
- y.observe(viewport, {
- attributes: true
- });
- y.observe(root, {
- attributes: true
- });
- var x = new MutationObserver(function () {
- var menu = document.querySelector('.mSideMenu');
- if (menu) {
- x.disconnect();
- console.log("Found side menu");
- while (root.firstChild) {
- root.removeChild(root.firstChild);
- }
- while (menu.childNodes.length) {
- viewport.appendChild(menu.childNodes[0]);
- }
- Frost.emit(0);
- setTimeout(function () {
- y.disconnect();
- console.log('Unhook styler');
- }, 500);
- }
- });
- x.observe(jewel, {
- childList: true,
- subtree: true
- });
- menuA.click();
-}).call(undefined);
diff --git a/app/src/web/assets/js/notif_msg.js b/app/src/web/assets/js/notif_msg.js
deleted file mode 100644
index bcff697b..00000000
--- a/app/src/web/assets/js/notif_msg.js
+++ /dev/null
@@ -1,25 +0,0 @@
-"use strict";
-(function () {
- var finished = false;
- var x = new MutationObserver(function () {
- var _f_thread = document.querySelector('#threadlist_rows');
- if (!_f_thread) {
- return;
- }
- console.log("Found message threads " + _f_thread.outerHTML);
- Frost.handleHtml(_f_thread.outerHTML);
- finished = true;
- x.disconnect();
- });
- x.observe(document, {
- childList: true,
- subtree: true
- });
- setTimeout(function () {
- if (!finished) {
- finished = true;
- console.log('Message thread timeout cancellation');
- Frost.handleHtml("");
- }
- }, 20000);
-}).call(undefined);
diff --git a/app/src/web/assets/js/textarea_listener.js b/app/src/web/assets/js/textarea_listener.js
deleted file mode 100644
index 1ec9b663..00000000
--- a/app/src/web/assets/js/textarea_listener.js
+++ /dev/null
@@ -1,23 +0,0 @@
-"use strict";
-(function () {
- var _frostFocus = function (e) {
- var element = e.target || e.srcElement;
- if (!(element instanceof Element)) {
- return;
- }
- console.log("FrostJSI focus, " + element.tagName);
- if (element.tagName === 'TEXTAREA') {
- Frost.disableSwipeRefresh(true);
- }
- };
- var _frostBlur = function (e) {
- var element = e.target || e.srcElement;
- if (!(element instanceof Element)) {
- return;
- }
- console.log("FrostJSI blur, " + element.tagName);
- Frost.disableSwipeRefresh(false);
- };
- document.addEventListener("focus", _frostFocus, true);
- document.addEventListener("blur", _frostBlur, true);
-}).call(undefined);
diff --git a/app/src/web/package-lock.json b/app/src/web/package-lock.json
new file mode 100644
index 00000000..8c7e9b78
--- /dev/null
+++ b/app/src/web/package-lock.json
@@ -0,0 +1,1630 @@
+{
+ "requires": true,
+ "lockfileVersion": 1,
+ "dependencies": {
+ "anymatch": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
+ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
+ "requires": {
+ "micromatch": "^3.1.4",
+ "normalize-path": "^2.1.1"
+ },
+ "dependencies": {
+ "normalize-path": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+ "requires": {
+ "remove-trailing-separator": "^1.0.1"
+ }
+ }
+ }
+ },
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA="
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg=="
+ },
+ "arr-union": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ="
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
+ },
+ "assign-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
+ },
+ "async-each": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
+ "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ=="
+ },
+ "atob": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
+ },
+ "base": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+ "requires": {
+ "cache-base": "^1.0.1",
+ "class-utils": "^0.3.5",
+ "component-emitter": "^1.2.1",
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.1",
+ "mixin-deep": "^1.2.0",
+ "pascalcase": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "binary-extensions": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
+ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw=="
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "cache-base": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+ "requires": {
+ "collection-visit": "^1.0.0",
+ "component-emitter": "^1.2.1",
+ "get-value": "^2.0.6",
+ "has-value": "^1.0.0",
+ "isobject": "^3.0.1",
+ "set-value": "^2.0.0",
+ "to-object-path": "^0.3.0",
+ "union-value": "^1.0.0",
+ "unset-value": "^1.0.0"
+ }
+ },
+ "chokidar": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz",
+ "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==",
+ "requires": {
+ "anymatch": "^2.0.0",
+ "async-each": "^1.0.1",
+ "braces": "^2.3.2",
+ "fsevents": "^1.2.7",
+ "glob-parent": "^3.1.0",
+ "inherits": "^2.0.3",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "normalize-path": "^3.0.0",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.2.1",
+ "upath": "^1.1.1"
+ }
+ },
+ "class-utils": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+ "requires": {
+ "arr-union": "^3.1.0",
+ "define-property": "^0.2.5",
+ "isobject": "^3.0.0",
+ "static-extend": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "collection-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+ "requires": {
+ "map-visit": "^1.0.0",
+ "object-visit": "^1.0.0"
+ }
+ },
+ "component-emitter": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+ "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
+ },
+ "copy-descriptor": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "decode-uri-component": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
+ },
+ "define-property": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+ "requires": {
+ "is-descriptor": "^1.0.2",
+ "isobject": "^3.0.1"
+ },
+ "dependencies": {
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "requires": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "extend-shallow": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+ "requires": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+ "requires": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "for-in": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
+ },
+ "fragment-cache": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+ "requires": {
+ "map-cache": "^0.2.2"
+ }
+ },
+ "fsevents": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.8.tgz",
+ "integrity": "sha512-tPvHgPGB7m40CZ68xqFGkKuzN+RnpGmSV+hgeKxhRpbxdqKXUFJGC3yonBOLzQBcJyGpdZFDfCsdOC2KFsXzeA==",
+ "optional": true,
+ "requires": {
+ "nan": "^2.12.1",
+ "node-pre-gyp": "^0.12.0"
+ },
+ "dependencies": {
+ "abbrev": {
+ "version": "1.1.1",
+ "bundled": true,
+ "optional": true
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "bundled": true,
+ "optional": true
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "bundled": true,
+ "optional": true
+ },
+ "are-we-there-yet": {
+ "version": "1.1.5",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "chownr": {
+ "version": "1.1.1",
+ "bundled": true,
+ "optional": true
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "bundled": true,
+ "optional": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "bundled": true,
+ "optional": true
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "bundled": true,
+ "optional": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "debug": {
+ "version": "4.1.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "deep-extend": {
+ "version": "0.6.0",
+ "bundled": true,
+ "optional": true
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "detect-libc": {
+ "version": "1.0.3",
+ "bundled": true,
+ "optional": true
+ },
+ "fs-minipass": {
+ "version": "1.2.5",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.2.1"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
+ "glob": {
+ "version": "7.1.3",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "bundled": true,
+ "optional": true
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "ignore-walk": {
+ "version": "3.0.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "minimatch": "^3.0.4"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "bundled": true,
+ "optional": true
+ },
+ "ini": {
+ "version": "1.3.5",
+ "bundled": true,
+ "optional": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "bundled": true,
+ "optional": true
+ },
+ "minipass": {
+ "version": "2.3.5",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.0"
+ }
+ },
+ "minizlib": {
+ "version": "1.2.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.2.1"
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "bundled": true,
+ "optional": true
+ },
+ "needle": {
+ "version": "2.3.0",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "debug": "^4.1.0",
+ "iconv-lite": "^0.4.4",
+ "sax": "^1.2.4"
+ }
+ },
+ "node-pre-gyp": {
+ "version": "0.12.0",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "detect-libc": "^1.0.2",
+ "mkdirp": "^0.5.1",
+ "needle": "^2.2.1",
+ "nopt": "^4.0.1",
+ "npm-packlist": "^1.1.6",
+ "npmlog": "^4.0.2",
+ "rc": "^1.2.7",
+ "rimraf": "^2.6.1",
+ "semver": "^5.3.0",
+ "tar": "^4"
+ }
+ },
+ "nopt": {
+ "version": "4.0.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "abbrev": "1",
+ "osenv": "^0.1.4"
+ }
+ },
+ "npm-bundled": {
+ "version": "1.0.6",
+ "bundled": true,
+ "optional": true
+ },
+ "npm-packlist": {
+ "version": "1.4.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "ignore-walk": "^3.0.1",
+ "npm-bundled": "^1.0.1"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "bundled": true,
+ "optional": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "bundled": true,
+ "optional": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "osenv": {
+ "version": "0.1.5",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.0"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "bundled": true,
+ "optional": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "rc": {
+ "version": "1.2.8",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "bundled": true,
+ "optional": true
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "rimraf": {
+ "version": "2.6.3",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true,
+ "optional": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "bundled": true,
+ "optional": true
+ },
+ "sax": {
+ "version": "1.2.4",
+ "bundled": true,
+ "optional": true
+ },
+ "semver": {
+ "version": "5.7.0",
+ "bundled": true,
+ "optional": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "bundled": true,
+ "optional": true
+ },
+ "tar": {
+ "version": "4.4.8",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "chownr": "^1.1.1",
+ "fs-minipass": "^1.2.5",
+ "minipass": "^2.3.4",
+ "minizlib": "^1.1.1",
+ "mkdirp": "^0.5.0",
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.2"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "wide-align": {
+ "version": "1.1.3",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "yallist": {
+ "version": "3.0.3",
+ "bundled": true,
+ "optional": true
+ }
+ }
+ },
+ "get-value": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
+ },
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "requires": {
+ "is-glob": "^3.1.0",
+ "path-dirname": "^1.0.0"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "requires": {
+ "is-extglob": "^2.1.0"
+ }
+ }
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
+ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
+ },
+ "has-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+ "requires": {
+ "get-value": "^2.0.6",
+ "has-values": "^1.0.0",
+ "isobject": "^3.0.0"
+ }
+ },
+ "has-values": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+ "requires": {
+ "is-number": "^3.0.0",
+ "kind-of": "^4.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-binary-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+ "requires": {
+ "binary-extensions": "^1.0.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="
+ }
+ }
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
+ },
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
+ },
+ "map-cache": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8="
+ },
+ "map-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+ "requires": {
+ "object-visit": "^1.0.0"
+ }
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
+ }
+ },
+ "mixin-deep": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
+ "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
+ "requires": {
+ "for-in": "^1.0.2",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "nan": {
+ "version": "2.13.2",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
+ "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==",
+ "optional": true
+ },
+ "nanomatch": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "fragment-cache": "^0.2.1",
+ "is-windows": "^1.0.2",
+ "kind-of": "^6.0.2",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ }
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
+ },
+ "object-copy": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+ "requires": {
+ "copy-descriptor": "^0.1.0",
+ "define-property": "^0.2.5",
+ "kind-of": "^3.0.3"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "object-visit": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+ "requires": {
+ "isobject": "^3.0.0"
+ }
+ },
+ "object.pick": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "pascalcase": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
+ },
+ "path-dirname": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA="
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+ },
+ "posix-character-classes": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "readdirp": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
+ "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+ "requires": {
+ "graceful-fs": "^4.1.11",
+ "micromatch": "^3.1.10",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "regex-not": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+ "requires": {
+ "extend-shallow": "^3.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "remove-trailing-separator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8="
+ },
+ "repeat-element": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+ "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g=="
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
+ },
+ "resolve-url": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
+ },
+ "ret": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "safe-regex": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+ "requires": {
+ "ret": "~0.1.10"
+ }
+ },
+ "sass": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.19.0.tgz",
+ "integrity": "sha512-8kzKCgxCzh8/zEn3AuRwzLWVSSFj8omkiGwqdJdeOufjM+I88dXxu9LYJ/Gw4rRTHXesN0r1AixBuqM6yLQUJw==",
+ "requires": {
+ "chokidar": "^2.0.0"
+ }
+ },
+ "set-value": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
+ "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.3",
+ "split-string": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "snapdragon": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+ "requires": {
+ "base": "^0.11.1",
+ "debug": "^2.2.0",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "map-cache": "^0.2.2",
+ "source-map": "^0.5.6",
+ "source-map-resolve": "^0.5.0",
+ "use": "^3.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "snapdragon-node": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+ "requires": {
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.0",
+ "snapdragon-util": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "snapdragon-util": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+ "requires": {
+ "kind-of": "^3.2.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+ },
+ "source-map-resolve": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
+ "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
+ "requires": {
+ "atob": "^2.1.1",
+ "decode-uri-component": "^0.2.0",
+ "resolve-url": "^0.2.1",
+ "source-map-url": "^0.4.0",
+ "urix": "^0.1.0"
+ }
+ },
+ "source-map-url": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM="
+ },
+ "split-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+ "requires": {
+ "extend-shallow": "^3.0.0"
+ }
+ },
+ "static-extend": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+ "requires": {
+ "define-property": "^0.2.5",
+ "object-copy": "^0.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "to-object-path": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "to-regex": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+ "requires": {
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "regex-not": "^1.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "to-regex-range": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+ "requires": {
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1"
+ }
+ },
+ "typescript": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.1.tgz",
+ "integrity": "sha512-cTmIDFW7O0IHbn1DPYjkiebHxwtCMU+eTy30ZtJNBPF9j2O1ITu5XH2YnBeVRKWHqF+3JQwWJv0Q0aUgX8W7IA=="
+ },
+ "union-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
+ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
+ "requires": {
+ "arr-union": "^3.1.0",
+ "get-value": "^2.0.6",
+ "is-extendable": "^0.1.1",
+ "set-value": "^0.4.3"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "set-value": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
+ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.1",
+ "to-object-path": "^0.3.0"
+ }
+ }
+ }
+ },
+ "unset-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+ "requires": {
+ "has-value": "^0.3.1",
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "has-value": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+ "requires": {
+ "get-value": "^2.0.3",
+ "has-values": "^0.1.4",
+ "isobject": "^2.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ }
+ }
+ },
+ "has-values": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+ "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E="
+ }
+ }
+ },
+ "upath": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz",
+ "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q=="
+ },
+ "urix": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI="
+ },
+ "use": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ }
+ }
+}
diff --git a/app/src/web/package.json b/app/src/web/package.json
index c80696b3..fbdc2442 100644
--- a/app/src/web/package.json
+++ b/app/src/web/package.json
@@ -1,5 +1,9 @@
{
+ "scripts": {
+ "compile": "tsc -p tsconfig.json && sass --no-source-map --style compressed --update scss:assets/css"
+ },
"dependencies": {
- "typescript": "^3.3.1"
+ "typescript": "^3.3.1",
+ "sass": "^1.19.0"
}
}
diff --git a/app/src/web/assets/css/core/_base.scss b/app/src/web/scss/core/_base.scss
index 472319fe..472319fe 100644
--- a/app/src/web/assets/css/core/_base.scss
+++ b/app/src/web/scss/core/_base.scss
diff --git a/app/src/web/assets/css/core/_colors.scss b/app/src/web/scss/core/_colors.scss
index 7610572c..7610572c 100644
--- a/app/src/web/assets/css/core/_colors.scss
+++ b/app/src/web/scss/core/_colors.scss
diff --git a/app/src/web/assets/css/core/_core_bg.scss b/app/src/web/scss/core/_core_bg.scss
index 494ee0c1..494ee0c1 100644
--- a/app/src/web/assets/css/core/_core_bg.scss
+++ b/app/src/web/scss/core/_core_bg.scss
diff --git a/app/src/web/assets/css/core/_core_border.scss b/app/src/web/scss/core/_core_border.scss
index 9f2bdec0..9f2bdec0 100644
--- a/app/src/web/assets/css/core/_core_border.scss
+++ b/app/src/web/scss/core/_core_border.scss
diff --git a/app/src/web/assets/css/core/_core_messenger.scss b/app/src/web/scss/core/_core_messenger.scss
index 608fc23d..608fc23d 100644
--- a/app/src/web/assets/css/core/_core_messenger.scss
+++ b/app/src/web/scss/core/_core_messenger.scss
diff --git a/app/src/web/assets/css/core/_core_text.scss b/app/src/web/scss/core/_core_text.scss
index 63622610..63622610 100644
--- a/app/src/web/assets/css/core/_core_text.scss
+++ b/app/src/web/scss/core/_core_text.scss
diff --git a/app/src/web/assets/css/core/_main.scss b/app/src/web/scss/core/_main.scss
index 3e972f93..3e972f93 100644
--- a/app/src/web/assets/css/core/_main.scss
+++ b/app/src/web/scss/core/_main.scss
diff --git a/app/src/web/assets/css/core/_svg.scss b/app/src/web/scss/core/_svg.scss
index 8c714438..8c714438 100644
--- a/app/src/web/assets/css/core/_svg.scss
+++ b/app/src/web/scss/core/_svg.scss
diff --git a/app/src/web/assets/css/core/core.scss b/app/src/web/scss/core/core.scss
index 38086529..38086529 100644
--- a/app/src/web/assets/css/core/core.scss
+++ b/app/src/web/scss/core/core.scss
diff --git a/app/src/web/assets/css/themes/.gitignore b/app/src/web/scss/themes/.gitignore
index 01d06441..4c46adff 100644
--- a/app/src/web/assets/css/themes/.gitignore
+++ b/app/src/web/scss/themes/.gitignore
@@ -1,2 +1 @@
test.scss
-test.css \ No newline at end of file
diff --git a/app/src/web/assets/css/themes/custom.scss b/app/src/web/scss/themes/custom.scss
index 50c029fb..50c029fb 100644
--- a/app/src/web/assets/css/themes/custom.scss
+++ b/app/src/web/scss/themes/custom.scss
diff --git a/app/src/web/assets/css/themes/material_amoled.scss b/app/src/web/scss/themes/material_amoled.scss
index 19190126..19190126 100644
--- a/app/src/web/assets/css/themes/material_amoled.scss
+++ b/app/src/web/scss/themes/material_amoled.scss
diff --git a/app/src/web/assets/css/themes/material_dark.scss b/app/src/web/scss/themes/material_dark.scss
index 18b8b461..18b8b461 100644
--- a/app/src/web/assets/css/themes/material_dark.scss
+++ b/app/src/web/scss/themes/material_dark.scss
diff --git a/app/src/web/assets/css/themes/material_glass.scss b/app/src/web/scss/themes/material_glass.scss
index 0c61a38c..0c61a38c 100644
--- a/app/src/web/assets/css/themes/material_glass.scss
+++ b/app/src/web/scss/themes/material_glass.scss
diff --git a/app/src/web/assets/css/themes/material_light.scss b/app/src/web/scss/themes/material_light.scss
index 7ec58463..7ec58463 100644
--- a/app/src/web/assets/css/themes/material_light.scss
+++ b/app/src/web/scss/themes/material_light.scss
diff --git a/app/src/web/assets/js/click_a.ts b/app/src/web/ts/click_a.ts
index 5023610e..5023610e 100644
--- a/app/src/web/assets/js/click_a.ts
+++ b/app/src/web/ts/click_a.ts
diff --git a/app/src/web/assets/js/click_debugger.ts b/app/src/web/ts/click_debugger.ts
index 088271fa..088271fa 100644
--- a/app/src/web/assets/js/click_debugger.ts
+++ b/app/src/web/ts/click_debugger.ts
diff --git a/app/src/web/assets/js/context_a.ts b/app/src/web/ts/context_a.ts
index 5eec7611..5eec7611 100644
--- a/app/src/web/assets/js/context_a.ts
+++ b/app/src/web/ts/context_a.ts
diff --git a/app/src/web/assets/js/document_watcher.ts b/app/src/web/ts/document_watcher.ts
index e671149c..e671149c 100644
--- a/app/src/web/assets/js/document_watcher.ts
+++ b/app/src/web/ts/document_watcher.ts
diff --git a/app/src/web/assets/js/header_badges.ts b/app/src/web/ts/header_badges.ts
index 473749f2..473749f2 100644
--- a/app/src/web/assets/js/header_badges.ts
+++ b/app/src/web/ts/header_badges.ts
diff --git a/app/src/web/assets/js/media.ts b/app/src/web/ts/media.ts
index 5b9b1a54..5b9b1a54 100644
--- a/app/src/web/assets/js/media.ts
+++ b/app/src/web/ts/media.ts
diff --git a/app/src/web/assets/js/menu.ts b/app/src/web/ts/menu.ts
index 6f9dbf16..6f9dbf16 100644
--- a/app/src/web/assets/js/menu.ts
+++ b/app/src/web/ts/menu.ts
diff --git a/app/src/web/assets/js/notif_msg.ts b/app/src/web/ts/notif_msg.ts
index b7ce7a19..b7ce7a19 100644
--- a/app/src/web/assets/js/notif_msg.ts
+++ b/app/src/web/ts/notif_msg.ts
diff --git a/app/src/web/assets/js/textarea_listener.ts b/app/src/web/ts/textarea_listener.ts
index 062f5bf6..062f5bf6 100644
--- a/app/src/web/assets/js/textarea_listener.ts
+++ b/app/src/web/ts/textarea_listener.ts
diff --git a/app/src/web/tsconfig.json b/app/src/web/tsconfig.json
index ea88e28e..6ef2a0b5 100644
--- a/app/src/web/tsconfig.json
+++ b/app/src/web/tsconfig.json
@@ -3,23 +3,21 @@
"target": "es3",
"allowJs": true,
"skipLibCheck": true,
-// "esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
-// "resolveJsonModule": true,
"isolatedModules": false,
-// "noEmit": true,
// Extras
"strictNullChecks": true,
"noImplicitAny": true,
"allowUnreachableCode": true,
"allowUnusedLabels": true,
- "removeComments": true
+ "removeComments": true,
+ "outDir": "assets/js"
},
"include": [
- "assets/js", "assets/typings"
+ "ts", "typings"
]
}
diff --git a/app/src/web/assets/typings/frost.d.ts b/app/src/web/typings/frost.d.ts
index 8f60c9dd..8f60c9dd 100644
--- a/app/src/web/assets/typings/frost.d.ts
+++ b/app/src/web/typings/frost.d.ts
diff --git a/crowdin.yaml b/crowdin.yaml
deleted file mode 100644
index 5053e211..00000000
--- a/crowdin.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-"project_identifier": "frost-for-facebook"
-"base_path": "/app/src/main/res"
-
-"files": [
- {
- "source" : "/values/strings.xml",
- "translation" : "/values-%android_code%/%original_file_name%"
- },
- {
- "source" : "/values/strings_*.xml",
- "translation" : "/values-%android_code%/%original_file_name%",
- "ignore" : [
- "/values/strings_no_translate.xml"
- ]
- }
-] \ No newline at end of file
diff --git a/crowdin.yml b/crowdin.yml
new file mode 100644
index 00000000..b0b57281
--- /dev/null
+++ b/crowdin.yml
@@ -0,0 +1,5 @@
+files:
+ - source: /app/src/main/res/values/strings*.xml
+ ignore:
+ - /app/src/main/res/values/strings_no_translate.xml
+ translation: /app/src/main/res/values-%android_code%/%original_file_name%
diff --git a/docker_build.sh b/docker_build.sh
new file mode 100755
index 00000000..37945b35
--- /dev/null
+++ b/docker_build.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+printf "Starting docker build\n"
+
+npm -v
+./gradlew -v
+
+npm install --prefix app/src/web
+npm run --prefix app/src/web compile
+
+./gradlew --quiet androidGitVersion
+./gradlew lintReleaseTest testReleaseUnitTest assembleReleaseTest \ No newline at end of file
diff --git a/docs/Changelog.md b/docs/Changelog.md
index 7a6bbfab..39e7fa82 100644
--- a/docs/Changelog.md
+++ b/docs/Changelog.md
@@ -1,5 +1,9 @@
# Changelog
+## v2.3.0
+* Converted internals of Facebook data storage; auto migration will only work from 2.2.x to 2.3.x
+* Added notification widget
+
## v2.2.4
* Show top bar to allow sharing posts
* Fix unmuting videos when autoplay is enabled
diff --git a/favicon/android-chrome-192x192.png b/favicon/android-chrome-192x192.png
new file mode 100644
index 00000000..49791799
--- /dev/null
+++ b/favicon/android-chrome-192x192.png
Binary files differ
diff --git a/favicon/android-chrome-512x512.png b/favicon/android-chrome-512x512.png
new file mode 100644
index 00000000..960b1818
--- /dev/null
+++ b/favicon/android-chrome-512x512.png
Binary files differ
diff --git a/favicon/apple-touch-icon.png b/favicon/apple-touch-icon.png
new file mode 100644
index 00000000..1fa3adf2
--- /dev/null
+++ b/favicon/apple-touch-icon.png
Binary files differ
diff --git a/favicon/browserconfig.xml b/favicon/browserconfig.xml
new file mode 100644
index 00000000..ed5dd798
--- /dev/null
+++ b/favicon/browserconfig.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+ <msapplication>
+ <tile>
+ <square150x150logo src="favicon/mstile-150x150.png"/>
+ <TileColor>#da532c</TileColor>
+ </tile>
+ </msapplication>
+</browserconfig>
diff --git a/favicon/favicon-16x16.png b/favicon/favicon-16x16.png
new file mode 100644
index 00000000..e43b849d
--- /dev/null
+++ b/favicon/favicon-16x16.png
Binary files differ
diff --git a/favicon/favicon-32x32.png b/favicon/favicon-32x32.png
new file mode 100644
index 00000000..bef47432
--- /dev/null
+++ b/favicon/favicon-32x32.png
Binary files differ
diff --git a/favicon/favicon.ico b/favicon/favicon.ico
new file mode 100644
index 00000000..ec0bf22c
--- /dev/null
+++ b/favicon/favicon.ico
Binary files differ
diff --git a/favicon/mstile-144x144.png b/favicon/mstile-144x144.png
new file mode 100644
index 00000000..5e4aa92d
--- /dev/null
+++ b/favicon/mstile-144x144.png
Binary files differ
diff --git a/favicon/mstile-150x150.png b/favicon/mstile-150x150.png
new file mode 100644
index 00000000..da592590
--- /dev/null
+++ b/favicon/mstile-150x150.png
Binary files differ
diff --git a/favicon/mstile-310x150.png b/favicon/mstile-310x150.png
new file mode 100644
index 00000000..6dcf8af0
--- /dev/null
+++ b/favicon/mstile-310x150.png
Binary files differ
diff --git a/favicon/mstile-310x310.png b/favicon/mstile-310x310.png
new file mode 100644
index 00000000..c93cc9e1
--- /dev/null
+++ b/favicon/mstile-310x310.png
Binary files differ
diff --git a/favicon/mstile-70x70.png b/favicon/mstile-70x70.png
new file mode 100644
index 00000000..d2e06ef6
--- /dev/null
+++ b/favicon/mstile-70x70.png
Binary files differ
diff --git a/favicon/safari-pinned-tab.svg b/favicon/safari-pinned-tab.svg
new file mode 100644
index 00000000..a0b0fb4c
--- /dev/null
+++ b/favicon/safari-pinned-tab.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+</metadata>
+<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
+fill="#000000" stroke="none">
+<path d="M3404 5926 c-27 -13 -74 -38 -104 -56 -30 -18 -68 -41 -85 -50 -16
+-10 -81 -47 -143 -84 -62 -36 -132 -77 -155 -90 -40 -22 -452 -263 -657 -384
+-128 -75 -158 -98 -183 -146 l-22 -41 0 -1940 0 -1940 22 -35 c42 -66 115
+-103 189 -96 71 7 122 44 160 116 17 33 18 88 19 1033 0 548 3 997 7 997 4 0
+31 -14 60 -31 51 -31 81 -48 151 -87 17 -11 276 -161 573 -335 298 -173 560
+-322 584 -330 53 -18 103 -12 154 16 117 65 133 225 32 314 -24 21 -61 47 -82
+59 -35 19 -979 567 -1254 729 -63 37 -135 79 -160 92 -25 14 -50 31 -55 38 -7
+8 -10 230 -10 625 l0 612 185 109 c102 60 221 130 265 155 44 25 94 53 110 63
+30 18 108 63 359 209 l140 81 30 -18 c39 -24 435 -254 861 -501 17 -10 71 -41
+120 -70 50 -29 133 -77 185 -108 52 -30 152 -88 221 -128 153 -89 197 -103
+264 -84 58 16 99 50 125 101 35 68 21 166 -31 218 -25 26 -241 161 -257 161
+-3 0 -13 6 -21 14 -9 7 -41 27 -71 43 -70 38 -94 52 -275 158 -176 104 -610
+356 -675 393 -25 14 -73 43 -108 64 -34 21 -64 38 -66 38 -2 0 -35 19 -72 42
+-164 100 -238 117 -330 74z"/>
+</g>
+</svg>
diff --git a/favicon/site.webmanifest b/favicon/site.webmanifest
new file mode 100644
index 00000000..1edcfeff
--- /dev/null
+++ b/favicon/site.webmanifest
@@ -0,0 +1,19 @@
+{
+ "name": "",
+ "short_name": "",
+ "icons": [
+ {
+ "src": "android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
diff --git a/files/translation_migration.sh b/files/translation_migration.sh
index 174d568a..03e3592a 100644
--- a/files/translation_migration.sh
+++ b/files/translation_migration.sh
@@ -6,11 +6,11 @@ cd ..
current=${PWD##*/}
-if [ "$current" != "$MODULE" ]; then
+if [[ "$current" != "$MODULE" ]]; then
echo "Not in $MODULE";
else
# DANGEROUS! Removes all files matching regex
- egrep -lir --include="*.xml" "<resources.*></resources>" "./" | tr '\n' '\0' | xargs -0 -n1 rm
+ grep -Lir "</string>" --include="strings*.xml" "app/src/main/res" | tr '\n' '\0' | xargs -0 -n1 rm
# Delete empty directories
find . -type d -empty -delete
fi
diff --git a/generate-apk-release.sh b/generate-apk-release.sh
index 00091f4b..d3a44577 100644..100755
--- a/generate-apk-release.sh
+++ b/generate-apk-release.sh
@@ -12,12 +12,17 @@ MODULE_NAME=app
VERSION_KEY=Frost
# Make version key different from module name
+# APK is directly moved by docker
# create a new directory that will contain our generated apk
-mkdir $HOME/$VERSION_KEY/
+# mkdir $HOME/$VERSION_KEY/
# copy generated apk from build folder to the folder just created
-cp -a $MODULE_NAME/build/outputs/apk/releaseTest/. $HOME/$VERSION_KEY/
-printf "Moved apks\n"
+# cp -a $MODULE_NAME/build/outputs/apk/releaseTest/. $HOME/$VERSION_KEY/
+# printf "Moved apks\n"
ls -a $HOME/${VERSION_KEY}
+if [ -z "$(find $HOME/${VERSION_KEY} -name '*.apk')" ]; then
+ echo "No apks found"
+ exit 1
+fi
# go to home and setup git
echo "Clone Git"
diff --git a/gradle.properties b/gradle.properties
index 44af6213..fe5a0971 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -11,6 +11,8 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+org.gradle.daemon = true
+
APP_ID=Frost
APP_GROUP=com.pitchedapps
@@ -58,6 +60,8 @@ MATERIAL_DRAWER_KT=2.0.1
# https://github.com/square/okhttp/releases
OKHTTP=3.14.0
# http://robolectric.org/getting-started/
+# https://developer.android.com/jetpack/androidx/releases/room
+ROOM=2.1.0-alpha04
ROBOELECTRIC=4.2
# https://github.com/davemorrissey/subsampling-scale-image-view#quick-start
SCALE_IMAGE_VIEW=3.10.0