diff options
author | Allan Wang <me@allanwang.ca> | 2019-05-01 16:05:19 -0700 |
---|---|---|
committer | Allan Wang <me@allanwang.ca> | 2019-05-01 16:05:19 -0700 |
commit | 58f4f9298b09081b3c937227828824849057a835 (patch) | |
tree | 18d2bf44637b52c9f68817253fc37d4ca699daef | |
parent | 576cc1a451a16f2d82ee1e41e83c420a85ded47e (diff) | |
parent | da93672c2ed6b54e0e7119a6b55715185112df3e (diff) | |
download | frost-58f4f9298b09081b3c937227828824849057a835.tar.gz frost-58f4f9298b09081b3c937227828824849057a835.tar.bz2 frost-58f4f9298b09081b3c937227828824849057a835.zip |
Merge dev
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 @@ -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 — 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 Binary files differnew file mode 100644 index 00000000..49791799 --- /dev/null +++ b/favicon/android-chrome-192x192.png diff --git a/favicon/android-chrome-512x512.png b/favicon/android-chrome-512x512.png Binary files differnew file mode 100644 index 00000000..960b1818 --- /dev/null +++ b/favicon/android-chrome-512x512.png diff --git a/favicon/apple-touch-icon.png b/favicon/apple-touch-icon.png Binary files differnew file mode 100644 index 00000000..1fa3adf2 --- /dev/null +++ b/favicon/apple-touch-icon.png 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 Binary files differnew file mode 100644 index 00000000..e43b849d --- /dev/null +++ b/favicon/favicon-16x16.png diff --git a/favicon/favicon-32x32.png b/favicon/favicon-32x32.png Binary files differnew file mode 100644 index 00000000..bef47432 --- /dev/null +++ b/favicon/favicon-32x32.png diff --git a/favicon/favicon.ico b/favicon/favicon.ico Binary files differnew file mode 100644 index 00000000..ec0bf22c --- /dev/null +++ b/favicon/favicon.ico diff --git a/favicon/mstile-144x144.png b/favicon/mstile-144x144.png Binary files differnew file mode 100644 index 00000000..5e4aa92d --- /dev/null +++ b/favicon/mstile-144x144.png diff --git a/favicon/mstile-150x150.png b/favicon/mstile-150x150.png Binary files differnew file mode 100644 index 00000000..da592590 --- /dev/null +++ b/favicon/mstile-150x150.png diff --git a/favicon/mstile-310x150.png b/favicon/mstile-310x150.png Binary files differnew file mode 100644 index 00000000..6dcf8af0 --- /dev/null +++ b/favicon/mstile-310x150.png diff --git a/favicon/mstile-310x310.png b/favicon/mstile-310x310.png Binary files differnew file mode 100644 index 00000000..c93cc9e1 --- /dev/null +++ b/favicon/mstile-310x310.png diff --git a/favicon/mstile-70x70.png b/favicon/mstile-70x70.png Binary files differnew file mode 100644 index 00000000..d2e06ef6 --- /dev/null +++ b/favicon/mstile-70x70.png 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 |