From c917dc13dabe7781a097383ae89f2d00f32fffcb Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 5 Mar 2019 18:31:47 -0500 Subject: Create initial room models --- app/build.gradle | 10 ++ .../com/pitchedapps/frost/db/FrostDatabaseTest.kt | 46 ++++++++ app/src/main/AndroidManifest.xml | 1 - .../main/kotlin/com/pitchedapps/frost/FrostApp.kt | 14 ++- .../kotlin/com/pitchedapps/frost/StartActivity.kt | 4 +- .../frost/activities/BaseMainActivity.kt | 6 +- .../pitchedapps/frost/activities/LoginActivity.kt | 6 +- .../frost/activities/TabCustomizerActivity.kt | 6 +- .../kotlin/com/pitchedapps/frost/db/CookiesDb.kt | 126 +++++++++++++++++++++ .../kotlin/com/pitchedapps/frost/db/Database.kt | 62 ++++++++++ .../kotlin/com/pitchedapps/frost/db/DbUtils.kt | 38 +++++++ .../kotlin/com/pitchedapps/frost/db/FbTabsDb.kt | 108 ++++++++++++++++++ .../com/pitchedapps/frost/db/NotificationDb.kt | 72 ++++++++++++ .../com/pitchedapps/frost/dbflow/CookiesDb.kt | 92 --------------- .../kotlin/com/pitchedapps/frost/dbflow/DbUtils.kt | 39 ------- .../com/pitchedapps/frost/dbflow/FbTabsDb.kt | 58 ---------- .../com/pitchedapps/frost/dbflow/NotificationDb.kt | 72 ------------ .../com/pitchedapps/frost/facebook/FbCookie.kt | 8 +- .../frost/facebook/parsers/FrostParser.kt | 2 +- .../frost/facebook/parsers/MessageParser.kt | 2 +- .../frost/facebook/parsers/NotifParser.kt | 2 +- .../frost/services/FrostNotifications.kt | 6 +- .../frost/services/NotificationService.kt | 4 +- .../pitchedapps/frost/settings/Notifications.kt | 4 +- .../com/pitchedapps/frost/utils/Downloader.kt | 2 +- .../kotlin/com/pitchedapps/frost/utils/Utils.kt | 2 +- .../com/pitchedapps/frost/views/AccountItem.kt | 2 +- .../kotlin/com/pitchedapps/frost/web/FrostJSI.kt | 2 +- .../com/pitchedapps/frost/web/LoginWebView.kt | 2 +- .../1.json | 45 ++++++++ .../1.json | 39 +++++++ build.gradle | 2 +- docs/Changelog.md | 4 + gradle.properties | 4 +- 34 files changed, 595 insertions(+), 297 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/db/DbUtils.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/dbflow/DbUtils.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/dbflow/NotificationDb.kt create mode 100644 app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json create mode 100644 app/src/schemas/com.pitchedapps.frost.db.FrostPublicDatabase/1.json diff --git a/app/build.gradle b/app/build.gradle index 4025568a..4c22ab97 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,6 +26,11 @@ android { versionName androidGitVersion.name() multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + javaCompileOptions { + annotationProcessorOptions { + arguments = ["room.schemaLocation": "$projectDir/src/schemas".toString()] + } + } } applicationVariants.all { variant -> @@ -252,6 +257,11 @@ dependencies { implementation "com.sothree.slidinguppanel:library:${SLIDING_PANEL}" + implementation "androidx.room:room-coroutines:${ROOM}" + implementation "android.arch.persistence.room:runtime:${ROOM}" + kapt "android.arch.persistence.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/FrostDatabaseTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt new file mode 100644 index 00000000..6b68e804 --- /dev/null +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt @@ -0,0 +1,46 @@ +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 kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals + +@RunWith(AndroidJUnit4::class) +class FrostDatabaseTest { + + private lateinit var db: FrostDatabase + + @Before + fun before() { + + val context = ApplicationProvider.getApplicationContext() + val privateDb = Room.inMemoryDatabaseBuilder( + context, FrostPrivateDatabase::class.java + ).build() + val publicDb = Room.inMemoryDatabaseBuilder( + context, FrostPublicDatabase::class.java + ).build() + db = FrostDatabase(privateDb, publicDb) + } + + @After + fun after() { + db.close() + } + + @Test + fun basic() { + val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") + runBlocking { + db.cookieDao().insertCookie(cookie) + val cookies = db.cookieDao().selectAll() + assertEquals(listOf(cookie), cookies, "Cookie mismatch") + } + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ad1fcbdc..68e84e4d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,7 +17,6 @@ . + */ +package com.pitchedapps.frost.db + +import android.os.Parcelable +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.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. + */ + +@Entity(tableName = "cookies") +@Parcelize +data class CookieEntity( + @androidx.room.PrimaryKey + var id: Long, + var name: String, + var cookie: String +) : Parcelable { + override fun toString(): String = "CookieModel(${hashCode()})" + + fun toSensitiveString(): String = "CookieModel(id=$id, name=$name, cookie=$cookie)" +} + +@Dao +interface CookieDao { + + @Query("SELECT * FROM cookies") + suspend fun selectAll(): List + + @Query("SELECT * FROM cookies WHERE id = :id") + suspend fun selectById(id: Long): CookieEntity? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertCookie(cookie: CookieEntity) + + @Query("DELETE FROM cookies WHERE id = :id") + suspend fun deleteById(id: Long) +} + +@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) -> Unit) { + (select from CookieModel::class).orderBy(CookieModel_Table.name, true).async() + .queryListResultCallback { _, tResult -> callback(tResult) }.execute() +} + +fun loadFbCookiesSync(): List = + (select from CookieModel::class).orderBy(CookieModel_Table.name, true).queryList() + +// TODO temp method until dbflow supports coroutines +suspend fun loadFbCookiesSuspend(): List = 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/db/Database.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt new file mode 100644 index 00000000..34de5e07 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt @@ -0,0 +1,62 @@ +package com.pitchedapps.frost.db + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters + +interface FrostPrivateDao { + fun cookieDao(): CookieDao +} + +@Database(entities = [CookieEntity::class], version = 1, exportSchema = true) +abstract class FrostPrivateDatabase : RoomDatabase(), FrostPrivateDao { + companion object { + const val DATABASE_NAME = "frost-priv-db" + } +} + +interface FrostPublicDao { + fun tabDao(): FbTabDao +} + +@Database(entities = [FbTabEntity::class], version = 1, exportSchema = true) +@TypeConverters(FbItemConverter::class) +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 { + fun create(context: Context): FrostDatabase { + val privateDb = Room.databaseBuilder( + context, FrostPrivateDatabase::class.java, + FrostPrivateDatabase.DATABASE_NAME + ).build() + val publicDb = Room.databaseBuilder( + context, FrostPublicDatabase::class.java, + FrostPublicDatabase.DATABASE_NAME + ).build() + return FrostDatabase(privateDb, publicDb) + } + } +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/DbUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/DbUtils.kt new file mode 100644 index 00000000..03aac059 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/DbUtils.kt @@ -0,0 +1,38 @@ +/* + * 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 . + */ +package com.pitchedapps.frost.db + +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 List.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/db/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt new file mode 100644 index 00000000..2e2a9d62 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt @@ -0,0 +1,108 @@ +/* + * 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 . + */ +package com.pitchedapps.frost.db + +import androidx.room.Dao +import androidx.room.Entity +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +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. + */ + +@Entity(tableName = "tabs") +data class FbTabEntity(@androidx.room.PrimaryKey var position: Int, var tab: FbItem) + +@Dao +interface FbTabDao { + + @Query("SELECT * FROM tabs ORDER BY position ASC") + suspend fun _selectAll(): List + + @Query("DELETE FROM tabs") + suspend fun _deleteAll() + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun _insertAll(items: List) + + @Transaction + suspend fun _save(items: List) { + _deleteAll() + _insertAll(items) + } + +// suspend fun save(items: List) { +// _save((items.takeIf { it.isNotEmpty() } ?: defaultTabs()).mapIndexed { index, fbItem -> +// FbTabEntity( +// index, +// fbItem +// ) +// }) +// } +// +// suspend fun selectAll(): List = _selectAll().map { it.tab }.takeIf { it.isNotEmpty() } ?: defaultTabs() +} + +object FbItemConverter { + @androidx.room.TypeConverter + @JvmStatic + fun fromItem(item: FbItem): String = item.name + + @androidx.room.TypeConverter + @JvmStatic + fun toItem(value: String): FbItem = FbItem.valueOf(value) +} + +const val TAB_COUNT = 4 + +@Database(version = FbTabsDb.VERSION) +object FbTabsDb { + const val NAME = "FrostTabs" + const val VERSION = 1 +} + +@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 { + val tabs: List? = (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.save() { + database().beginTransactionAsync(mapIndexed(::FbTabModel).fastSave().build()).execute() +} 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..5b501792 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt @@ -0,0 +1,72 @@ +/* + * 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 . + */ +package com.pitchedapps.frost.db + +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) : + AlterTableMigration(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/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 . - */ -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) -> Unit) { - (select from CookieModel::class).orderBy(CookieModel_Table.name, true).async() - .queryListResultCallback { _, tResult -> callback(tResult) }.execute() -} - -fun loadFbCookiesSync(): List = - (select from CookieModel::class).orderBy(CookieModel_Table.name, true).queryList() - -// TODO temp method until dbflow supports coroutines -suspend fun loadFbCookiesSuspend(): List = 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 . - */ -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 List.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/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt deleted file mode 100644 index 9d330169..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt +++ /dev/null @@ -1,58 +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 . - */ -package com.pitchedapps.frost.dbflow - -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) -object FbTabsDb { - const val NAME = "FrostTabs" - const val VERSION = 1 -} - -@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 { - val tabs: List? = (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.save() { - database().beginTransactionAsync(mapIndexed(::FbTabModel).fastSave().build()).execute() -} 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 . - */ -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) : - AlterTableMigration(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..349b415c 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,10 @@ 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.CookieModel +import com.pitchedapps.frost.db.loadFbCookie +import com.pitchedapps.frost.db.removeCookie +import com.pitchedapps.frost.db.saveFbCookie import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.cookies 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..57e7cc94 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.CookieModel import com.pitchedapps.frost.facebook.FB_CSS_URL_MATCHER import com.pitchedapps.frost.facebook.formattedFbUrl import com.pitchedapps.frost.facebook.get 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..b2b59234 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.CookieModel import com.pitchedapps.frost.facebook.FB_EPOCH_MATCHER import com.pitchedapps.frost.facebook.FB_MESSAGE_NOTIF_ID_MATCHER import com.pitchedapps.frost.facebook.FbItem 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..0474e35e 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.CookieModel import com.pitchedapps.frost.facebook.FB_EPOCH_MATCHER import com.pitchedapps.frost.facebook.FB_NOTIF_ID_MATCHER import com.pitchedapps.frost.facebook.FbItem 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 f66f77e2..994f9a18 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,9 @@ 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.CookieModel +import com.pitchedapps.frost.db.NotificationModel +import com.pitchedapps.frost.db.lastNotificationTime import com.pitchedapps.frost.enums.OverlayContext import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.parsers.FrostParser 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..a895900f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -21,8 +21,8 @@ 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.CookieModel +import com.pitchedapps.frost.db.loadFbCookiesSync import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.frostEvent 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..774b0e7f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt @@ -29,8 +29,8 @@ 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.NotificationModel +import com.pitchedapps.frost.db.loadFbCookiesAsync import com.pitchedapps.frost.services.fetchNotifications import com.pitchedapps.frost.services.scheduleNotifications import com.pitchedapps.frost.utils.Prefs 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 f4baa242..254297a6 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.loadFbCookie import com.pitchedapps.frost.facebook.USER_AGENT_BASIC /** 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 4410b26e..1222e93b 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.WebOverlayBasicActivity -import com.pitchedapps.frost.dbflow.CookieModel +import com.pitchedapps.frost.db.CookieModel import com.pitchedapps.frost.facebook.FACEBOOK_COM import com.pitchedapps.frost.facebook.FBCDN_NET import com.pitchedapps.frost.facebook.FbCookie 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 d7a7de0e..8c7435ea 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.CookieModel import com.pitchedapps.frost.facebook.profilePictureUrl import com.pitchedapps.frost.glide.FrostGlide import com.pitchedapps.frost.glide.GlideApp 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..663acfbb 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.CookieModel import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs 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 c21ce93b..11a53c36 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.CookieModel import com.pitchedapps.frost.facebook.FB_LOGIN_URL import com.pitchedapps.frost.facebook.FB_USER_MATCHER import com.pitchedapps.frost.facebook.FbCookie 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..7bc2e5e9 --- /dev/null +++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json @@ -0,0 +1,45 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "6af67964d00193c6a3aa2a20200ea0ea", + "entities": [ + { + "tableName": "cookies", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cookie", + "columnName": "cookie", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "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, \"6af67964d00193c6a3aa2a20200ea0ea\")" + ] + } +} \ 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..687d0bc1 --- /dev/null +++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPublicDatabase/1.json @@ -0,0 +1,39 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "fde868470836ff9230f1d406922d7563", + "entities": [ + { + "tableName": "tabs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`position` INTEGER NOT NULL, `tab` TEXT NOT NULL, PRIMARY KEY(`position`))", + "fields": [ + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tab", + "columnName": "tab", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "position" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "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, \"fde868470836ff9230f1d406922d7563\")" + ] + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 64e324d2..22a0102b 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { dependencies { classpath "ca.allanwang:kau:${KAU}" - classpath 'com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta02' +// classpath 'com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta02' classpath "com.android.tools.build:gradle:${ANDROID_GRADLE}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${KOTLIN}" classpath "com.bugsnag:bugsnag-android-gradle-plugin:${BUGSNAG_PLUGIN}" diff --git a/docs/Changelog.md b/docs/Changelog.md index e58b8f5f..4d90b7fb 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,5 +1,9 @@ # Changelog +## v2.2.3 +* Add ability to hide stories +* Remove fbclid from urls + ## v2.2.2 * New marketplace shortcut * Fix crash when internet disconnects (may still need app restart) diff --git a/gradle.properties b/gradle.properties index f95fed2b..98f29dbe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,7 +15,7 @@ APP_ID=Frost APP_GROUP=com.pitchedapps KAU=4.0.0-alpha02 -KOTLIN=1.3.11 +KOTLIN=1.3.21 # https://mvnrepository.com/artifact/com.android.tools.build/gradle?repo=google ANDROID_GRADLE=3.2.1 @@ -57,6 +57,8 @@ MATERIAL_DRAWER_KT=2.0.1 OKHTTP=3.12.1 # http://robolectric.org/getting-started/ ROBOELECTRIC=4.1 +# https://developer.android.com/jetpack/androidx/releases/room +ROOM=2.1.0-alpha04 # https://github.com/davemorrissey/subsampling-scale-image-view#quick-start SCALE_IMAGE_VIEW=3.10.0 # https://github.com/umano/AndroidSlidingUpPanel#importing-the-library -- cgit v1.2.3 From ee9a9a696420d5da05b4f306898fff29e9abe1a4 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 5 Mar 2019 18:44:18 -0500 Subject: Create working dao without suspension --- .../kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt | 12 ++++++------ app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt | 8 -------- app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt | 8 ++++---- app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt | 8 ++++---- 4 files changed, 14 insertions(+), 22 deletions(-) diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt index 6b68e804..2d94ed97 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt @@ -5,10 +5,11 @@ import androidx.room.Room import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test +import org.junit.Rule import org.junit.runner.RunWith +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test import kotlin.test.assertEquals @RunWith(AndroidJUnit4::class) @@ -16,9 +17,8 @@ class FrostDatabaseTest { private lateinit var db: FrostDatabase - @Before + @BeforeTest fun before() { - val context = ApplicationProvider.getApplicationContext() val privateDb = Room.inMemoryDatabaseBuilder( context, FrostPrivateDatabase::class.java @@ -29,7 +29,7 @@ class FrostDatabaseTest { db = FrostDatabase(privateDb, publicDb) } - @After + @AfterTest fun after() { db.close() } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt index a7cd2777..ba92a345 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt @@ -44,7 +44,6 @@ 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 com.squareup.sqldelight.android.AndroidSqliteDriver import java.util.Random import kotlin.reflect.KClass @@ -115,13 +114,6 @@ class FrostApp : Application() { .thumbnail(old).into(imageView) } }) - val driver = AndroidSqliteDriver(Database.Schema, this, "test.db") - val db = Database(driver) - db.transaction { - db.frostQueries.selectAll().executeAsList().forEach { - it.id - } - } if (BuildConfig.DEBUG) registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { override fun onActivityPaused(activity: Activity) {} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt index 9deb57da..0b8283fd 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt @@ -60,16 +60,16 @@ data class CookieEntity( interface CookieDao { @Query("SELECT * FROM cookies") - suspend fun selectAll(): List + fun selectAll(): List @Query("SELECT * FROM cookies WHERE id = :id") - suspend fun selectById(id: Long): CookieEntity? + fun selectById(id: Long): CookieEntity? @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertCookie(cookie: CookieEntity) + fun insertCookie(cookie: CookieEntity) @Query("DELETE FROM cookies WHERE id = :id") - suspend fun deleteById(id: Long) + fun deleteById(id: Long) } @Database(version = CookiesDb.VERSION) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt index 2e2a9d62..ab01e025 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt @@ -45,16 +45,16 @@ data class FbTabEntity(@androidx.room.PrimaryKey var position: Int, var tab: FbI interface FbTabDao { @Query("SELECT * FROM tabs ORDER BY position ASC") - suspend fun _selectAll(): List + fun _selectAll(): List @Query("DELETE FROM tabs") - suspend fun _deleteAll() + fun _deleteAll() @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun _insertAll(items: List) + fun _insertAll(items: List) @Transaction - suspend fun _save(items: List) { + fun _save(items: List) { _deleteAll() _insertAll(items) } -- cgit v1.2.3 From e82b74a687f5b5bff6f7cc16e1ba504583a8db32 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 5 Mar 2019 18:46:42 -0500 Subject: Move helper functions outside of interface --- .../main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt index ab01e025..4fc38ec9 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt @@ -58,19 +58,19 @@ interface FbTabDao { _deleteAll() _insertAll(items) } +} -// suspend fun save(items: List) { -// _save((items.takeIf { it.isNotEmpty() } ?: defaultTabs()).mapIndexed { index, fbItem -> -// FbTabEntity( -// index, -// fbItem -// ) -// }) -// } -// -// suspend fun selectAll(): List = _selectAll().map { it.tab }.takeIf { it.isNotEmpty() } ?: defaultTabs() +suspend fun FbTabDao.save(items: List) { + _save((items.takeIf { it.isNotEmpty() } ?: defaultTabs()).mapIndexed { index, fbItem -> + FbTabEntity( + index, + fbItem + ) + }) } +suspend fun FbTabDao.selectAll(): List = _selectAll().map { it.tab }.takeIf { it.isNotEmpty() } ?: defaultTabs() + object FbItemConverter { @androidx.room.TypeConverter @JvmStatic -- cgit v1.2.3 From 8841728780438444bf51f1a2c3b0d961e49908d2 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 5 Mar 2019 18:52:47 -0500 Subject: Change dependencies and add back suspensions --- app/build.gradle | 4 ++-- app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt | 8 ++++---- app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 4c22ab97..82f0c6b1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -258,8 +258,8 @@ dependencies { implementation "com.sothree.slidinguppanel:library:${SLIDING_PANEL}" implementation "androidx.room:room-coroutines:${ROOM}" - implementation "android.arch.persistence.room:runtime:${ROOM}" - kapt "android.arch.persistence.room:compiler:${ROOM}" + implementation "androidx.room:room-runtime:${ROOM}" + kapt "androidx.room:room-compiler:${ROOM}" testImplementation "androidx.room:room-testing:${ROOM}" } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt index 0b8283fd..9deb57da 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt @@ -60,16 +60,16 @@ data class CookieEntity( interface CookieDao { @Query("SELECT * FROM cookies") - fun selectAll(): List + suspend fun selectAll(): List @Query("SELECT * FROM cookies WHERE id = :id") - fun selectById(id: Long): CookieEntity? + suspend fun selectById(id: Long): CookieEntity? @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertCookie(cookie: CookieEntity) + suspend fun insertCookie(cookie: CookieEntity) @Query("DELETE FROM cookies WHERE id = :id") - fun deleteById(id: Long) + suspend fun deleteById(id: Long) } @Database(version = CookiesDb.VERSION) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt index 4fc38ec9..ff64f5eb 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt @@ -45,16 +45,16 @@ data class FbTabEntity(@androidx.room.PrimaryKey var position: Int, var tab: FbI interface FbTabDao { @Query("SELECT * FROM tabs ORDER BY position ASC") - fun _selectAll(): List + suspend fun _selectAll(): List @Query("DELETE FROM tabs") - fun _deleteAll() + suspend fun _deleteAll() @Insert(onConflict = OnConflictStrategy.REPLACE) - fun _insertAll(items: List) + suspend fun _insertAll(items: List) @Transaction - fun _save(items: List) { + suspend fun _save(items: List) { _deleteAll() _insertAll(items) } -- cgit v1.2.3 From b5d442ba3c86500b23d5bff8f1eb80ab51d1ccfa Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 5 Mar 2019 19:13:57 -0500 Subject: Add more cookie db tests --- .../com/pitchedapps/frost/db/FrostDatabaseTest.kt | 41 ++++++++++++++++++++-- .../kotlin/com/pitchedapps/frost/db/FbTabsDb.kt | 31 +++++++++------- 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt index 2d94ed97..dcc96c2d 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt @@ -5,7 +5,6 @@ import androidx.room.Room import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.runBlocking -import org.junit.Rule import org.junit.runner.RunWith import kotlin.test.AfterTest import kotlin.test.BeforeTest @@ -35,7 +34,7 @@ class FrostDatabaseTest { } @Test - fun basic() { + fun basicCookie() { val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") runBlocking { db.cookieDao().insertCookie(cookie) @@ -43,4 +42,42 @@ class FrostDatabaseTest { assertEquals(listOf(cookie), cookies, "Cookie mismatch") } } + + @Test + fun deleteCookie() { + val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") + + runBlocking { + db.cookieDao().insertCookie(cookie) + db.cookieDao().deleteById(cookie.id + 1) + assertEquals( + listOf(cookie), + db.cookieDao().selectAll(), + "Cookie list should be the same after inexistent deletion" + ) + db.cookieDao().deleteById(cookie.id) + assertEquals(emptyList(), db.cookieDao().selectAll(), "Cookie list should be empty after deletion") + } + } + + @Test + fun insertCookie() { + val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") + runBlocking { + db.cookieDao().insertCookie(cookie) + assertEquals(listOf(cookie), db.cookieDao().selectAll(), "Cookie insertion failed") + db.cookieDao().insertCookie(cookie.copy(name = "testName2")) + assertEquals( + listOf(cookie.copy(name = "testName2")), + db.cookieDao().selectAll(), + "Cookie replacement failed" + ) + db.cookieDao().insertCookie(cookie.copy(id = 123L)) + assertEquals( + setOf(cookie.copy(id = 123L), cookie.copy(name = "testName2")), + db.cookieDao().selectAll().toSet(), + "New cookie insertion failed" + ) + } + } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt index ff64f5eb..44e62938 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt @@ -33,6 +33,8 @@ 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 +import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.withContext /** * Created by Allan Wang on 2017-05-30. @@ -52,21 +54,26 @@ interface FbTabDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun _insertAll(items: List) - - @Transaction - suspend fun _save(items: List) { - _deleteAll() - _insertAll(items) - } } +/** + * Saving tabs operates by deleting all db items and saving the new list. + * Transactions can't be done with suspensions in room as switching threads during the process + * may result in a deadlock. + * In this case, there may be a chance that the 'transaction' completes partially, + * but we'll just fallback to the default anyways. + */ suspend fun FbTabDao.save(items: List) { - _save((items.takeIf { it.isNotEmpty() } ?: defaultTabs()).mapIndexed { index, fbItem -> - FbTabEntity( - index, - fbItem - ) - }) + withContext(NonCancellable) { + _deleteAll() + val entities = (items.takeIf { it.isNotEmpty() } ?: defaultTabs()).mapIndexed { index, fbItem -> + FbTabEntity( + index, + fbItem + ) + } + _insertAll(entities) + } } suspend fun FbTabDao.selectAll(): List = _selectAll().map { it.tab }.takeIf { it.isNotEmpty() } ?: defaultTabs() -- cgit v1.2.3 From cd0e7549d4a9876923649b83bbc82dab9caa0232 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 5 Mar 2019 19:21:17 -0500 Subject: Split db tests per dao --- .../kotlin/com/pitchedapps/frost/db/BaseDbTest.kt | 32 +++++++++ .../com/pitchedapps/frost/db/CookieDbTest.kt | 67 +++++++++++++++++ .../com/pitchedapps/frost/db/FrostDatabaseTest.kt | 83 ---------------------- 3 files changed, 99 insertions(+), 83 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/pitchedapps/frost/db/BaseDbTest.kt create mode 100644 app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt delete mode 100644 app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt 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..6621ea95 --- /dev/null +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/BaseDbTest.kt @@ -0,0 +1,32 @@ +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() + 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() + } +} \ No newline at end of file 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..351490e2 --- /dev/null +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt @@ -0,0 +1,67 @@ +package com.pitchedapps.frost.db + +import kotlinx.coroutines.runBlocking +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class CookieDbTest : BaseDbTest() { + + @Test + fun basicCookie() { + val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") + runBlocking { + db.cookieDao().insertCookie(cookie) + val cookies = db.cookieDao().selectAll() + assertEquals(listOf(cookie), cookies, "Cookie mismatch") + } + } + + @Test + fun deleteCookie() { + val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") + + runBlocking { + db.cookieDao().insertCookie(cookie) + db.cookieDao().deleteById(cookie.id + 1) + assertEquals( + listOf(cookie), + db.cookieDao().selectAll(), + "Cookie list should be the same after inexistent deletion" + ) + db.cookieDao().deleteById(cookie.id) + assertEquals(emptyList(), db.cookieDao().selectAll(), "Cookie list should be empty after deletion") + } + } + + @Test + fun insertReplaceCookie() { + val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") + runBlocking { + db.cookieDao().insertCookie(cookie) + assertEquals(listOf(cookie), db.cookieDao().selectAll(), "Cookie insertion failed") + db.cookieDao().insertCookie(cookie.copy(name = "testName2")) + assertEquals( + listOf(cookie.copy(name = "testName2")), + db.cookieDao().selectAll(), + "Cookie replacement failed" + ) + db.cookieDao().insertCookie(cookie.copy(id = 123L)) + assertEquals( + setOf(cookie.copy(id = 123L), cookie.copy(name = "testName2")), + db.cookieDao().selectAll().toSet(), + "New cookie insertion failed" + ) + } + } + + @Test + fun selectCookie() { + val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") + runBlocking { + db.cookieDao().insertCookie(cookie) + assertEquals(cookie, db.cookieDao().selectById(cookie.id), "Cookie selection failed") + assertNull(db.cookieDao().selectById(cookie.id + 1), "Inexistent cookie selection failed") + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt deleted file mode 100644 index dcc96c2d..00000000 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FrostDatabaseTest.kt +++ /dev/null @@ -1,83 +0,0 @@ -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 kotlinx.coroutines.runBlocking -import org.junit.runner.RunWith -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class FrostDatabaseTest { - - private lateinit var db: FrostDatabase - - @BeforeTest - fun before() { - val context = ApplicationProvider.getApplicationContext() - 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() - } - - @Test - fun basicCookie() { - val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") - runBlocking { - db.cookieDao().insertCookie(cookie) - val cookies = db.cookieDao().selectAll() - assertEquals(listOf(cookie), cookies, "Cookie mismatch") - } - } - - @Test - fun deleteCookie() { - val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") - - runBlocking { - db.cookieDao().insertCookie(cookie) - db.cookieDao().deleteById(cookie.id + 1) - assertEquals( - listOf(cookie), - db.cookieDao().selectAll(), - "Cookie list should be the same after inexistent deletion" - ) - db.cookieDao().deleteById(cookie.id) - assertEquals(emptyList(), db.cookieDao().selectAll(), "Cookie list should be empty after deletion") - } - } - - @Test - fun insertCookie() { - val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") - runBlocking { - db.cookieDao().insertCookie(cookie) - assertEquals(listOf(cookie), db.cookieDao().selectAll(), "Cookie insertion failed") - db.cookieDao().insertCookie(cookie.copy(name = "testName2")) - assertEquals( - listOf(cookie.copy(name = "testName2")), - db.cookieDao().selectAll(), - "Cookie replacement failed" - ) - db.cookieDao().insertCookie(cookie.copy(id = 123L)) - assertEquals( - setOf(cookie.copy(id = 123L), cookie.copy(name = "testName2")), - db.cookieDao().selectAll().toSet(), - "New cookie insertion failed" - ) - } - } -} \ No newline at end of file -- cgit v1.2.3 From 5c4400975450c9739f0986561075983e08afae89 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 5 Mar 2019 20:03:55 -0500 Subject: Add fb tab test --- .../com/pitchedapps/frost/db/FbTabsDbTest.kt | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt new file mode 100644 index 00000000..a2dce692 --- /dev/null +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt @@ -0,0 +1,32 @@ +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 FbTabsDbTest : BaseDbTest() { + + /** + * 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 { + db.tabDao().save(tabs) + assertEquals(tabs, db.tabDao().selectAll(), "Tab saving failed") + val newTabs = listOf(FbItem.PAGES, FbItem.MENU) + db.tabDao().save(newTabs) + assertEquals(newTabs, db.tabDao().selectAll(), "Tab saving does not delete preexisting items") + } + } + + @Test + fun defaultRetrieve() { + runBlocking { + assertEquals(defaultTabs(), db.tabDao().selectAll(), "Default retrieval failed") + } + } +} \ No newline at end of file -- cgit v1.2.3 From 65bb9233b2a0d8734c1d13e8f3a01bee0f6c3b17 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 5 Mar 2019 21:06:24 -0500 Subject: Convert fbcookies to room entities --- app/build.gradle | 6 +-- .../main/kotlin/com/pitchedapps/frost/FrostApp.kt | 3 ++ .../kotlin/com/pitchedapps/frost/StartActivity.kt | 19 ++++++-- .../frost/activities/BaseMainActivity.kt | 54 +++++++++++++--------- .../pitchedapps/frost/activities/LoginActivity.kt | 15 +++--- .../kotlin/com/pitchedapps/frost/db/CookiesDb.kt | 50 ++++---------------- .../kotlin/com/pitchedapps/frost/db/Database.kt | 14 ++++++ .../com/pitchedapps/frost/facebook/FbCookie.kt | 31 ++++++++----- .../frost/facebook/parsers/FrostParser.kt | 3 +- .../frost/facebook/parsers/MessageParser.kt | 3 +- .../frost/facebook/parsers/NotifParser.kt | 3 +- .../frost/services/FrostNotifications.kt | 7 +-- .../frost/services/NotificationService.kt | 11 +++-- .../pitchedapps/frost/settings/Notifications.kt | 11 +++-- .../com/pitchedapps/frost/utils/Downloader.kt | 7 +-- .../kotlin/com/pitchedapps/frost/utils/Utils.kt | 10 ++-- .../com/pitchedapps/frost/views/AccountItem.kt | 4 +- .../pitchedapps/frost/views/FrostVideoViewer.kt | 11 ++++- .../kotlin/com/pitchedapps/frost/web/FrostJSI.kt | 3 +- .../com/pitchedapps/frost/web/LoginWebView.kt | 16 ++++--- 20 files changed, 158 insertions(+), 123 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 82f0c6b1..d5dcf87e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -204,9 +204,9 @@ dependencies { implementation "androidx.core:core-ktx:${KTX}" -// 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}" diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt index ba92a345..7669ef46 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt @@ -31,6 +31,7 @@ import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader import com.mikepenz.materialdrawer.util.DrawerImageLoader 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 @@ -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 @@ -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 86596843..fe859f95 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt @@ -32,6 +32,8 @@ 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.db.CookieDao +import com.pitchedapps.frost.db.CookieEntity import com.pitchedapps.frost.db.CookieModel import com.pitchedapps.frost.db.loadFbCookiesSync import com.pitchedapps.frost.facebook.FbCookie @@ -43,6 +45,7 @@ import com.pitchedapps.frost.utils.loadAssets import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.koin.android.ext.android.inject import java.util.ArrayList /** @@ -50,6 +53,8 @@ import java.util.ArrayList */ class StartActivity : KauBaseActivity() { + private val cookieDao: CookieDao by inject() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -67,12 +72,11 @@ class StartActivity : KauBaseActivity() { launch { 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() when { cookies.isEmpty() -> launchNewTask() @@ -90,6 +94,13 @@ class StartActivity : KauBaseActivity() { } } + /** + * Migrate from dbflow to room + */ + private suspend fun migrate() { + + } + 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 33fc2078..4669418d 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,11 @@ 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.db.CookieDao +import com.pitchedapps.frost.db.FbTabDao import com.pitchedapps.frost.db.TAB_COUNT -import com.pitchedapps.frost.db.loadFbCookie -import com.pitchedapps.frost.db.loadFbTabs +import com.pitchedapps.frost.db.currentCookie +import com.pitchedapps.frost.db.selectAll import com.pitchedapps.frost.enums.MainActivityLayout import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.facebook.FbItem @@ -109,6 +111,7 @@ 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. @@ -123,6 +126,8 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, protected lateinit var adapter: SectionsPagerAdapter override val frameWrapper: FrameLayout get() = frame_wrapper val viewPager: FrostViewPager get() = container + val cookieDao: CookieDao by inject() + val tabDao: FbTabDao by inject() /* * Components with the same id in multiple layout files @@ -151,9 +156,11 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, background(viewPager) } setSupportActionBar(toolbar) - adapter = SectionsPagerAdapter(loadFbTabs()) - viewPager.adapter = adapter - viewPager.offscreenPageLimit = TAB_COUNT + launch { + adapter = SectionsPagerAdapter(tabDao.selectAll()) + 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" } @@ -274,27 +281,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 + 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) + } } } } 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 7ff22a5a..5649cc73 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,8 @@ 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.db.CookieModel -import com.pitchedapps.frost.db.loadFbCookiesSuspend -import com.pitchedapps.frost.db.saveFbCookie +import com.pitchedapps.frost.db.CookieDao +import com.pitchedapps.frost.db.CookieEntity import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.profilePictureUrl @@ -58,6 +57,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 +71,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(10) @@ -109,7 +110,7 @@ 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) } @@ -134,7 +135,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(cookies, true) @@ -171,7 +172,7 @@ class LoginActivity : BaseActivity() { } } - private suspend fun loadUsername(cookie: CookieModel): String = withContext(Dispatchers.IO) { + private suspend fun loadUsername(cookie: CookieEntity): String = withContext(Dispatchers.IO) { val result: String = try { withTimeout(5000) { frostJsoup(cookie.cookie, FbItem.PROFILE.url).title() @@ -184,7 +185,7 @@ class LoginActivity : BaseActivity() { if (cookie.name?.isNotBlank() == false && result != cookie.name) { cookie.name = result - saveFbCookie(cookie) + cookieDao.insertCookie(cookie) } cookie.name ?: "" diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt index 9deb57da..fb240fd3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt @@ -23,6 +23,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import com.pitchedapps.frost.utils.L +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 @@ -48,12 +49,12 @@ import kotlinx.coroutines.withContext data class CookieEntity( @androidx.room.PrimaryKey var id: Long, - var name: String, - var cookie: String + var name: String?, + var cookie: String? ) : Parcelable { - override fun toString(): String = "CookieModel(${hashCode()})" + override fun toString(): String = "CookieEntity(${hashCode()})" - fun toSensitiveString(): String = "CookieModel(id=$id, name=$name, cookie=$cookie)" + fun toSensitiveString(): String = "CookieEntity(id=$id, name=$name, cookie=$cookie)" } @Dao @@ -72,6 +73,8 @@ interface CookieDao { suspend fun deleteById(id: Long) } +suspend fun CookieDao.currentCookie() = selectById(Prefs.userId) + @Database(version = CookiesDb.VERSION) object CookiesDb { const val NAME = "Cookies" @@ -86,41 +89,4 @@ data class CookieModel(@PrimaryKey var id: Long = -1L, var name: String? = null, 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) -> Unit) { - (select from CookieModel::class).orderBy(CookieModel_Table.name, true).async() - .queryListResultCallback { _, tResult -> callback(tResult) }.execute() -} - -fun loadFbCookiesSync(): List = - (select from CookieModel::class).orderBy(CookieModel_Table.name, true).queryList() - -// TODO temp method until dbflow supports coroutines -suspend fun loadFbCookiesSuspend(): List = 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 } - } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt index 34de5e07..161ed93d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt @@ -5,6 +5,8 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters +import org.koin.dsl.module.module +import org.koin.standalone.StandAloneContext interface FrostPrivateDao { fun cookieDao(): CookieDao @@ -58,5 +60,17 @@ class FrostDatabase(private val privateDb: FrostPrivateDatabase, private val pub ).build() return FrostDatabase(privateDb, publicDb) } + + fun module(context: Context) = module { + single { create(context) } + single { get().cookieDao() } + single { get().tabDao() } + } + + /** + * 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/facebook/FbCookie.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt index 349b415c..6f79da43 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,10 @@ package com.pitchedapps.frost.facebook import android.app.Activity import android.content.Context import android.webkit.CookieManager -import com.pitchedapps.frost.db.CookieModel -import com.pitchedapps.frost.db.loadFbCookie +import com.pitchedapps.frost.db.CookieDao +import com.pitchedapps.frost.db.CookieEntity +import com.pitchedapps.frost.db.FrostDatabase import com.pitchedapps.frost.db.removeCookie -import com.pitchedapps.frost.db.saveFbCookie import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.cookies @@ -50,6 +50,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 +81,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.insertCookie(cookie) } suspend fun reset() { @@ -93,11 +97,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 +119,7 @@ object FbCookie { * and launch the proper login page */ suspend fun logout(context: Context) { - val cookies = arrayListOf() + val cookies = arrayListOf() if (context is Activity) cookies.addAll(context.cookies().filter { it.id != Prefs.userId }) logout(Prefs.userId) @@ -126,7 +131,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/parsers/FrostParser.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/FrostParser.kt index 57e7cc94..bf89a103 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,6 +16,7 @@ */ package com.pitchedapps.frost.facebook.parsers +import com.pitchedapps.frost.db.CookieEntity import com.pitchedapps.frost.db.CookieModel import com.pitchedapps.frost.facebook.FB_CSS_URL_MATCHER import com.pitchedapps.frost.facebook.formattedFbUrl @@ -81,7 +82,7 @@ data class ParseResponse(val cookie: String, val data: T) { } interface ParseNotification { - fun getUnreadNotifications(data: CookieModel): List + fun getUnreadNotifications(data: CookieEntity): List } internal fun List.toJsonString(tag: String, indent: Int) = StringBuilder().apply { 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 b2b59234..866cab10 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,6 +16,7 @@ */ package com.pitchedapps.frost.facebook.parsers +import com.pitchedapps.frost.db.CookieEntity import com.pitchedapps.frost.db.CookieModel import com.pitchedapps.frost.facebook.FB_EPOCH_MATCHER import com.pitchedapps.frost.facebook.FB_MESSAGE_NOTIF_ID_MATCHER @@ -54,7 +55,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( 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 0474e35e..3449ac48 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,6 +16,7 @@ */ package com.pitchedapps.frost.facebook.parsers +import com.pitchedapps.frost.db.CookieEntity import com.pitchedapps.frost.db.CookieModel import com.pitchedapps.frost.facebook.FB_EPOCH_MATCHER import com.pitchedapps.frost.facebook.FB_NOTIF_ID_MATCHER @@ -43,7 +44,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( 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 994f9a18..abd871b3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt @@ -31,6 +31,7 @@ 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.db.CookieEntity import com.pitchedapps.frost.db.CookieModel import com.pitchedapps.frost.db.NotificationModel import com.pitchedapps.frost.db.lastNotificationTime @@ -116,7 +117,7 @@ enum class NotificationType( * Returns the number of notifications generated, * or -1 if an error occurred */ - fun fetch(context: Context, data: CookieModel): Int { + fun fetch(context: Context, data: CookieEntity): Int { val response = try { parser.parse(data.cookie) } catch (ignored: Exception) { @@ -161,7 +162,7 @@ enum class NotificationType( return notifs.size } - fun debugNotification(context: Context, data: CookieModel) { + fun debugNotification(context: Context, data: CookieEntity) { val content = NotificationContent( data, System.currentTimeMillis(), @@ -247,7 +248,7 @@ enum class NotificationType( * Notification data holder */ data class NotificationContent( - val data: CookieModel, + val data: CookieEntity, val id: Long, val href: String, val title: String? = null, // defaults to frost title 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 a895900f..088d8e0a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -21,8 +21,8 @@ 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.db.CookieModel -import com.pitchedapps.frost.db.loadFbCookiesSync +import com.pitchedapps.frost.db.CookieDao +import com.pitchedapps.frost.db.CookieEntity import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.frostEvent @@ -31,6 +31,7 @@ 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 +43,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 +84,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 @@ -107,7 +110,7 @@ class NotificationService : BaseJobService() { * 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 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 774b0e7f..3444d0b0 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.db.FrostDatabase import com.pitchedapps.frost.db.NotificationModel -import com.pitchedapps.frost.db.loadFbCookiesAsync 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,12 @@ 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() + .cookieDao() + .selectAll() + .map { NotificationModel(it.id) } + .forEach { it.save() } } } } 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 254297a6..64094d1e 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.db.loadFbCookie +import com.pitchedapps.frost.db.CookieEntity import com.pitchedapps.frost.facebook.USER_AGENT_BASIC /** @@ -38,6 +38,7 @@ import com.pitchedapps.frost.facebook.USER_AGENT_BASIC * With reference to Stack Overflow */ fun Context.frostDownload( + cookie: CookieEntity, url: String?, userAgent: String = USER_AGENT_BASIC, 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_BASIC, 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/Utils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt index 1222e93b..5a104458 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.WebOverlayBasicActivity -import com.pitchedapps.frost.db.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 Context.launchNewTask( - cookieList: ArrayList = arrayListOf(), + cookieList: ArrayList = arrayListOf(), clearStack: Boolean = false ) { startActivity(clearStack, intentBuilder = { @@ -111,13 +111,13 @@ inline fun Context.launchNewTask( }) } -fun Context.launchLogin(cookieList: ArrayList, clearStack: Boolean = true) { +fun Context.launchLogin(cookieList: ArrayList, clearStack: Boolean = true) { if (cookieList.isNotEmpty()) launchNewTask(cookieList, clearStack) else launchNewTask(clearStack = clearStack) } -fun Activity.cookies(): ArrayList { - return intent?.getParcelableArrayListExtra(EXTRA_COOKIES) ?: arrayListOf() +fun Activity.cookies(): ArrayList { + return intent?.getParcelableArrayListExtra(EXTRA_COOKIES) ?: arrayListOf() } /** 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 8c7435ea..07eaed0a 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.db.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 +class AccountItem(val cookie: CookieEntity?) : KauIItem (R.layout.view_account, { ViewHolder(it) }, R.id.item_account) { override fun bindView(viewHolder: ViewHolder, payloads: MutableList) { 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..288e2b40 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,12 @@ 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.CookieModel_Table.cookie +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 +101,11 @@ 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/web/FrostJSI.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt index 663acfbb..4df84b63 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt @@ -21,6 +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.db.CookieEntity import com.pitchedapps.frost.db.CookieModel import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.utils.L @@ -44,7 +45,7 @@ class FrostJSI(val web: FrostWebView) { private val activity: MainActivity? = context as? MainActivity private val header: SendChannel? = activity?.headerBadgeChannel private val refresh: SendChannel = web.parent.refreshChannel - private val cookies: List = activity?.cookies() ?: arrayListOf() + private val cookies: List = 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 11a53c36..07aafaf0 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt @@ -29,6 +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.db.CookieEntity import com.pitchedapps.frost.db.CookieModel import com.pitchedapps.frost.facebook.FB_LOGIN_URL import com.pitchedapps.frost.facebook.FB_USER_MATCHER @@ -52,7 +53,7 @@ class LoginWebView @JvmOverloads constructor( defStyleAttr: Int = 0 ) : WebView(context, attrs, defStyleAttr) { - private val completable: CompletableDeferred = CompletableDeferred() + private val completable: CompletableDeferred = CompletableDeferred() private lateinit var progressCallback: (Int) -> Unit @SuppressLint("SetJavaScriptEnabled") @@ -63,7 +64,7 @@ class LoginWebView @JvmOverloads constructor( webChromeClient = LoginChromeClient() } - suspend fun loadLogin(progressCallback: (Int) -> Unit): CompletableDeferred = coroutineScope { + suspend fun loadLogin(progressCallback: (Int) -> Unit): CompletableDeferred = coroutineScope { this@LoginWebView.progressCallback = progressCallback L.d { "Begin loading login" } launchMain { @@ -78,18 +79,19 @@ 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) + // TODO set name to null? + return CookieEntity(id, "", cookie) } override fun onPageCommitVisible(view: WebView, url: String?) { -- cgit v1.2.3 From 9a1d9719ad6559054ea1bc4f21f8559559eb9cda Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 5 Mar 2019 21:25:46 -0500 Subject: Port rest of cookie items --- app/src/main/AndroidManifest.xml | 1 + app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt | 12 +++++++++--- app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt | 13 +++---------- .../main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt | 1 - .../main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt | 12 +++++++++++- .../com.pitchedapps.frost.db.FrostPrivateDatabase/1.json | 11 ++++++----- 6 files changed, 30 insertions(+), 20 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 68e84e4d..ad1fcbdc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ ) + @Query("DELETE FROM cookies WHERE id = :id") suspend fun deleteById(id: Long) } 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 6f79da43..02718b78 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt @@ -22,7 +22,6 @@ import android.webkit.CookieManager import com.pitchedapps.frost.db.CookieDao import com.pitchedapps.frost.db.CookieEntity import com.pitchedapps.frost.db.FrostDatabase -import com.pitchedapps.frost.db.removeCookie import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.cookies 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 0e9d94e6..594b0acd 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_BASIC import com.pitchedapps.frost.facebook.USER_AGENT_FULL 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 @@ -80,7 +84,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/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json index 7bc2e5e9..9816651c 100644 --- a/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json +++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "6af67964d00193c6a3aa2a20200ea0ea", + "identityHash": "ba6f1d7e47823dac6ed1622fec043d5d", "entities": [ { "tableName": "cookies", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`id`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `cookie` TEXT, PRIMARY KEY(`id`))", "fields": [ { "fieldPath": "id", @@ -18,13 +18,13 @@ "fieldPath": "name", "columnName": "name", "affinity": "TEXT", - "notNull": true + "notNull": false }, { "fieldPath": "cookie", "columnName": "cookie", "affinity": "TEXT", - "notNull": true + "notNull": false } ], "primaryKey": { @@ -37,9 +37,10 @@ "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, \"6af67964d00193c6a3aa2a20200ea0ea\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"ba6f1d7e47823dac6ed1622fec043d5d\")" ] } } \ No newline at end of file -- cgit v1.2.3 From 8b70d80070209eb19791eecf207a8fdefea17a4e Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 6 Mar 2019 17:42:31 -0500 Subject: Make db entities immutable --- .../com/pitchedapps/frost/db/CookieDbTest.kt | 34 +++--- .../com/pitchedapps/frost/db/FbTabsDbTest.kt | 12 +- .../com/pitchedapps/frost/db/NotificationDbTest.kt | 20 ++++ .../pitchedapps/frost/activities/LoginActivity.kt | 3 +- .../kotlin/com/pitchedapps/frost/db/CookiesDb.kt | 6 +- .../kotlin/com/pitchedapps/frost/db/Database.kt | 7 +- .../kotlin/com/pitchedapps/frost/db/FbTabsDb.kt | 28 +++-- .../com/pitchedapps/frost/db/NotificationDb.kt | 64 +++++++++++ .../1.json | 125 ++++++++++++++++++++- 9 files changed, 259 insertions(+), 40 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt index 351490e2..20592347 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt @@ -6,13 +6,15 @@ 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 { - db.cookieDao().insertCookie(cookie) - val cookies = db.cookieDao().selectAll() + dao.insertCookie(cookie) + val cookies = dao.selectAll() assertEquals(listOf(cookie), cookies, "Cookie mismatch") } } @@ -22,15 +24,15 @@ class CookieDbTest : BaseDbTest() { val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") runBlocking { - db.cookieDao().insertCookie(cookie) - db.cookieDao().deleteById(cookie.id + 1) + dao.insertCookie(cookie) + dao.deleteById(cookie.id + 1) assertEquals( listOf(cookie), - db.cookieDao().selectAll(), + dao.selectAll(), "Cookie list should be the same after inexistent deletion" ) - db.cookieDao().deleteById(cookie.id) - assertEquals(emptyList(), db.cookieDao().selectAll(), "Cookie list should be empty after deletion") + dao.deleteById(cookie.id) + assertEquals(emptyList(), dao.selectAll(), "Cookie list should be empty after deletion") } } @@ -38,18 +40,18 @@ class CookieDbTest : BaseDbTest() { fun insertReplaceCookie() { val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") runBlocking { - db.cookieDao().insertCookie(cookie) - assertEquals(listOf(cookie), db.cookieDao().selectAll(), "Cookie insertion failed") - db.cookieDao().insertCookie(cookie.copy(name = "testName2")) + dao.insertCookie(cookie) + assertEquals(listOf(cookie), dao.selectAll(), "Cookie insertion failed") + dao.insertCookie(cookie.copy(name = "testName2")) assertEquals( listOf(cookie.copy(name = "testName2")), - db.cookieDao().selectAll(), + dao.selectAll(), "Cookie replacement failed" ) - db.cookieDao().insertCookie(cookie.copy(id = 123L)) + dao.insertCookie(cookie.copy(id = 123L)) assertEquals( setOf(cookie.copy(id = 123L), cookie.copy(name = "testName2")), - db.cookieDao().selectAll().toSet(), + dao.selectAll().toSet(), "New cookie insertion failed" ) } @@ -59,9 +61,9 @@ class CookieDbTest : BaseDbTest() { fun selectCookie() { val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") runBlocking { - db.cookieDao().insertCookie(cookie) - assertEquals(cookie, db.cookieDao().selectById(cookie.id), "Cookie selection failed") - assertNull(db.cookieDao().selectById(cookie.id + 1), "Inexistent cookie selection failed") + dao.insertCookie(cookie) + assertEquals(cookie, dao.selectById(cookie.id), "Cookie selection failed") + assertNull(dao.selectById(cookie.id + 1), "Inexistent cookie selection failed") } } } \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt index a2dce692..91a0bf9a 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt @@ -7,6 +7,8 @@ import kotlin.test.Test import kotlin.test.assertEquals class FbTabsDbTest : BaseDbTest() { + + private val dao get() = db.tabDao() /** * Note that order is also preserved here @@ -15,18 +17,18 @@ class FbTabsDbTest : BaseDbTest() { fun save() { val tabs = listOf(FbItem.ACTIVITY_LOG, FbItem.BIRTHDAYS, FbItem.EVENTS, FbItem.MARKETPLACE, FbItem.ACTIVITY_LOG) runBlocking { - db.tabDao().save(tabs) - assertEquals(tabs, db.tabDao().selectAll(), "Tab saving failed") + dao.save(tabs) + assertEquals(tabs, dao.selectAll(), "Tab saving failed") val newTabs = listOf(FbItem.PAGES, FbItem.MENU) - db.tabDao().save(newTabs) - assertEquals(newTabs, db.tabDao().selectAll(), "Tab saving does not delete preexisting items") + dao.save(newTabs) + assertEquals(newTabs, dao.selectAll(), "Tab saving does not delete preexisting items") } } @Test fun defaultRetrieve() { runBlocking { - assertEquals(defaultTabs(), db.tabDao().selectAll(), "Default retrieval failed") + assertEquals(defaultTabs(), dao.selectAll(), "Default retrieval failed") } } } \ No newline at end of file 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..12092bf6 --- /dev/null +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt @@ -0,0 +1,20 @@ +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 NotificationDbTest : BaseDbTest() { + + private val dao get() = db.notifDao() + + /** + * Note that order is also preserved here + */ + @Test + fun save() { + + } +} \ No newline at end of file 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 5649cc73..27dbc37a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt @@ -184,8 +184,7 @@ class LoginActivity : BaseActivity() { } if (cookie.name?.isNotBlank() == false && result != cookie.name) { - cookie.name = result - cookieDao.insertCookie(cookie) + cookieDao.insertCookie(cookie.copy(name = result)) } cookie.name ?: "" diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt index 128abae3..34a88011 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt @@ -38,9 +38,9 @@ import kotlinx.android.parcel.Parcelize @Parcelize data class CookieEntity( @androidx.room.PrimaryKey - var id: Long, - var name: String?, - var cookie: String? + val id: Long, + val name: String?, + val cookie: String? ) : Parcelable { override fun toString(): String = "CookieEntity(${hashCode()})" diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt index 161ed93d..a37c2ee9 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt @@ -10,9 +10,14 @@ import org.koin.standalone.StandAloneContext interface FrostPrivateDao { fun cookieDao(): CookieDao + fun notifDao(): NotificationDao } -@Database(entities = [CookieEntity::class], version = 1, exportSchema = true) +@Database( + entities = [CookieEntity::class, NotificationInfoEntity::class, NotificationEntity::class], + version = 1, + exportSchema = true +) abstract class FrostPrivateDatabase : RoomDatabase(), FrostPrivateDao { companion object { const val DATABASE_NAME = "frost-priv-db" diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt index 44e62938..582d57fb 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt @@ -33,7 +33,7 @@ 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 -import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext /** @@ -41,42 +41,48 @@ import kotlinx.coroutines.withContext */ @Entity(tableName = "tabs") -data class FbTabEntity(@androidx.room.PrimaryKey var position: Int, var tab: FbItem) +data class FbTabEntity(@androidx.room.PrimaryKey val position: Int, val tab: FbItem) @Dao interface FbTabDao { @Query("SELECT * FROM tabs ORDER BY position ASC") - suspend fun _selectAll(): List + fun _selectAll(): List @Query("DELETE FROM tabs") - suspend fun _deleteAll() + fun _deleteAll() @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun _insertAll(items: List) + fun _insertAll(items: List) + + @Transaction + fun _save(items: List) { + _deleteAll() + _insertAll(items) + } } /** * Saving tabs operates by deleting all db items and saving the new list. * Transactions can't be done with suspensions in room as switching threads during the process * may result in a deadlock. - * In this case, there may be a chance that the 'transaction' completes partially, - * but we'll just fallback to the default anyways. + * That's why we disallow thread switching within the transaction, but wrap the entire thing in a coroutine */ suspend fun FbTabDao.save(items: List) { - withContext(NonCancellable) { - _deleteAll() + withContext(Dispatchers.IO) { val entities = (items.takeIf { it.isNotEmpty() } ?: defaultTabs()).mapIndexed { index, fbItem -> FbTabEntity( index, fbItem ) } - _insertAll(entities) + _save(entities) } } -suspend fun FbTabDao.selectAll(): List = _selectAll().map { it.tab }.takeIf { it.isNotEmpty() } ?: defaultTabs() +suspend fun FbTabDao.selectAll(): List = withContext(Dispatchers.IO) { + _selectAll().map { it.tab }.takeIf { it.isNotEmpty() } ?: defaultTabs() +} object FbItemConverter { @androidx.room.TypeConverter diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt index 5b501792..56c3c5ac 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt @@ -16,6 +16,16 @@ */ package com.pitchedapps.frost.db +import androidx.room.Dao +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Ignore +import androidx.room.Index +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Relation +import androidx.room.Transaction import com.pitchedapps.frost.utils.L import com.raizlabs.android.dbflow.annotation.ConflictAction import com.raizlabs.android.dbflow.annotation.Database @@ -32,6 +42,60 @@ import com.raizlabs.android.dbflow.sql.SQLiteType import com.raizlabs.android.dbflow.sql.migration.AlterTableMigration import com.raizlabs.android.dbflow.structure.BaseModel +@Entity( + tableName = "notification_info", + foreignKeys = [ForeignKey( + entity = CookieEntity::class, + parentColumns = ["id"], childColumns = ["id"], onDelete = ForeignKey.CASCADE + )] +) +data class NotificationInfoEntity( + @androidx.room.PrimaryKey val id: Long, + val epoch: Long, + val epochIm: Long +) + +@Entity( + tableName = "notifications", + foreignKeys = [ForeignKey( + entity = NotificationInfoEntity::class, + parentColumns = ["id"], + childColumns = ["userId"], + onDelete = ForeignKey.CASCADE + )], + indices = [Index("userId")] +) +data class NotificationEntity( + @androidx.room.PrimaryKey var id: Long, + val userId: Long, + val href: String, + val title: String?, + val text: String, + val timestamp: Long, + val profileUrl: String? +) { + @Ignore + val notifId = Math.abs(id.toInt()) +} + +data class NotificationInfo( + @Embedded + val info: NotificationInfoEntity, + @Relation(parentColumn = "id", entityColumn = "userId") + val notifications: List = emptyList() +) + +@Dao +interface NotificationDao { + + @Query("SELECT * FROM notification_info WHERE id = :id") + fun selectById(id: Long): NotificationInfo? + + @Transaction + @Insert + fun insertInfo(info: NotificationInfoEntity) +} + /** * Created by Allan Wang on 2017-05-30. */ diff --git a/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json index 9816651c..f0909a17 100644 --- a/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json +++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "ba6f1d7e47823dac6ed1622fec043d5d", + "identityHash": "d0911840f629ef359aaf8ac8635dc571", "entities": [ { "tableName": "cookies", @@ -35,12 +35,133 @@ }, "indices": [], "foreignKeys": [] + }, + { + "tableName": "notification_info", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `epoch` INTEGER NOT NULL, `epochIm` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`id`) REFERENCES `cookies`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "epoch", + "columnName": "epoch", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "epochIm", + "columnName": "epochIm", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "cookies", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `userId` INTEGER NOT NULL, `href` TEXT NOT NULL, `title` TEXT, `text` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `profileUrl` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`userId`) REFERENCES `notification_info`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "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 + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_notifications_userId", + "unique": false, + "columnNames": [ + "userId" + ], + "createSql": "CREATE INDEX `index_notifications_userId` ON `${TABLE_NAME}` (`userId`)" + } + ], + "foreignKeys": [ + { + "table": "notification_info", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "userId" + ], + "referencedColumns": [ + "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, \"ba6f1d7e47823dac6ed1622fec043d5d\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"d0911840f629ef359aaf8ac8635dc571\")" ] } } \ No newline at end of file -- cgit v1.2.3 From b417cc51b28d558195c4cc075d0e6ce8192bf270 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 6 Mar 2019 23:35:04 -0500 Subject: Update notif database --- .../com/pitchedapps/frost/db/NotificationDbTest.kt | 81 ++++++++++++++- .../kotlin/com/pitchedapps/frost/db/CookiesDb.kt | 6 +- .../kotlin/com/pitchedapps/frost/db/Database.kt | 2 +- .../com/pitchedapps/frost/db/NotificationDb.kt | 112 +++++++++++++++------ .../frost/services/FrostNotifications.kt | 1 + .../1.json | 79 +++++---------- .../1.json | 1 + 7 files changed, 190 insertions(+), 92 deletions(-) diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt index 12092bf6..2e9f1875 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt @@ -1,20 +1,93 @@ package com.pitchedapps.frost.db -import com.pitchedapps.frost.facebook.FbItem -import com.pitchedapps.frost.facebook.defaultTabs +import android.database.sqlite.SQLiteConstraintException +import com.pitchedapps.frost.services.NOTIF_CHANNEL_GENERAL +import com.pitchedapps.frost.services.NotificationContent import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +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 + ) + + @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().insertCookie(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") + } + } + /** - * Note that order is also preserved here + * Primary key is both id and userId, in the event that the same notification to multiple users has the same id */ @Test - fun save() { + 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().insertCookie(cookie1) + db.cookieDao().insertCookie(cookie2) + dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs1) + dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs2) + } + } + @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().insertCookie(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().insertCookie(cookie) + dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs) + assertEquals(99L, dao.latestEpoch(cookie.id, NOTIF_CHANNEL_GENERAL), "Latest epoch failed") + } + } + + @Test + fun insertionWithInvalidCookies() { + assertFailsWith(SQLiteConstraintException::class) { + runBlocking { + dao.saveNotifications(NOTIF_CHANNEL_GENERAL, listOf(notifContent(1L, cookie(2L)))) + } + } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt index 34a88011..d5347f18 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt @@ -17,6 +17,7 @@ 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 @@ -38,6 +39,7 @@ import kotlinx.android.parcel.Parcelize @Parcelize data class CookieEntity( @androidx.room.PrimaryKey + @ColumnInfo(name = "cookie_id") val id: Long, val name: String?, val cookie: String? @@ -53,7 +55,7 @@ interface CookieDao { @Query("SELECT * FROM cookies") suspend fun selectAll(): List - @Query("SELECT * FROM cookies WHERE id = :id") + @Query("SELECT * FROM cookies WHERE cookie_id = :id") suspend fun selectById(id: Long): CookieEntity? @Insert(onConflict = OnConflictStrategy.REPLACE) @@ -62,7 +64,7 @@ interface CookieDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertCookies(cookies: List) - @Query("DELETE FROM cookies WHERE id = :id") + @Query("DELETE FROM cookies WHERE cookie_id = :id") suspend fun deleteById(id: Long) } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt index a37c2ee9..1ce9e7c2 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt @@ -14,7 +14,7 @@ interface FrostPrivateDao { } @Database( - entities = [CookieEntity::class, NotificationInfoEntity::class, NotificationEntity::class], + entities = [CookieEntity::class, NotificationEntity::class], version = 1, exportSchema = true ) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt index 56c3c5ac..9622ec47 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt @@ -16,16 +16,17 @@ */ 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.Ignore import androidx.room.Index import androidx.room.Insert +import androidx.room.OnConflictStrategy import androidx.room.Query -import androidx.room.Relation import androidx.room.Transaction +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 @@ -41,59 +42,108 @@ 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 = "notification_info", - foreignKeys = [ForeignKey( - entity = CookieEntity::class, - parentColumns = ["id"], childColumns = ["id"], onDelete = ForeignKey.CASCADE - )] -) -data class NotificationInfoEntity( - @androidx.room.PrimaryKey val id: Long, - val epoch: Long, - val epochIm: Long -) +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext @Entity( tableName = "notifications", + primaryKeys = ["notif_id", "userId"], foreignKeys = [ForeignKey( - entity = NotificationInfoEntity::class, - parentColumns = ["id"], + entity = CookieEntity::class, + parentColumns = ["cookie_id"], childColumns = ["userId"], onDelete = ForeignKey.CASCADE )], - indices = [Index("userId")] + indices = [Index("notif_id"), Index("userId")] ) data class NotificationEntity( - @androidx.room.PrimaryKey var id: Long, + @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? + val profileUrl: String?, + // Type essentially refers to channel + val type: String ) { - @Ignore - val notifId = Math.abs(id.toInt()) + constructor( + type: String, + content: NotificationContent + ) : this( + content.id, + content.data.id, + content.href, + content.title, + content.text, + content.timestamp, + content.profileUrl, + type + ) } -data class NotificationInfo( +data class NotificationContentEntity( @Embedded - val info: NotificationInfoEntity, - @Relation(parentColumn = "id", entityColumn = "userId") - val notifications: List = emptyList() -) + 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 + ) +} @Dao interface NotificationDao { - @Query("SELECT * FROM notification_info WHERE id = :id") - fun selectById(id: Long): NotificationInfo? + /** + * 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 + @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) + + @Query("DELETE FROM notifications WHERE userId = :userId AND type = :type") + fun _deleteNotifications(userId: Long, type: String) + + /** + * It is assumed that the notification batch comes from the same user + */ @Transaction - @Insert - fun insertInfo(info: NotificationInfoEntity) + fun _saveNotifications(type: String, notifs: List) { + val userId = notifs.firstOrNull()?.data?.id ?: return + val entities = notifs.map { NotificationEntity(type, it) } + _deleteNotifications(userId, type) + _insertNotifications(entities) + } +} + +suspend fun NotificationDao.selectNotifications(userId: Long, type: String): List = + withContext(Dispatchers.IO) { + _selectNotifications(userId, type).map { it.toNotifContent() } + } + +suspend fun NotificationDao.saveNotifications(type: String, notifs: List) { + withContext(Dispatchers.IO) { + _saveNotifications(type, notifs) + } +} + +suspend fun NotificationDao.latestEpoch(userId: Long, type: String): Long = withContext(Dispatchers.IO) { + _selectEpoch(userId, type) ?: -1 } /** 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 abd871b3..7da3c128 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt @@ -248,6 +248,7 @@ enum class NotificationType( * Notification data holder */ data class NotificationContent( + // TODO replace data with userId? val data: CookieEntity, val id: Long, val href: String, diff --git a/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json index f0909a17..c382bce7 100644 --- a/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json +++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json @@ -2,15 +2,15 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "d0911840f629ef359aaf8ac8635dc571", + "identityHash": "77eff76407f59b690b8877cc22fac42f", "entities": [ { "tableName": "cookies", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `cookie` TEXT, PRIMARY KEY(`id`))", + "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": "id", + "columnName": "cookie_id", "affinity": "INTEGER", "notNull": true }, @@ -29,64 +29,20 @@ ], "primaryKey": { "columnNames": [ - "id" + "cookie_id" ], "autoGenerate": false }, "indices": [], "foreignKeys": [] }, - { - "tableName": "notification_info", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `epoch` INTEGER NOT NULL, `epochIm` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`id`) REFERENCES `cookies`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "epoch", - "columnName": "epoch", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "epochIm", - "columnName": "epochIm", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "cookies", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "id" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, { "tableName": "notifications", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `userId` INTEGER NOT NULL, `href` TEXT NOT NULL, `title` TEXT, `text` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `profileUrl` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`userId`) REFERENCES `notification_info`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "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, PRIMARY KEY(`notif_id`, `userId`), FOREIGN KEY(`userId`) REFERENCES `cookies`(`cookie_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "id", - "columnName": "id", + "columnName": "notif_id", "affinity": "INTEGER", "notNull": true }, @@ -125,15 +81,30 @@ "columnName": "profileUrl", "affinity": "TEXT", "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true } ], "primaryKey": { "columnNames": [ - "id" + "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, @@ -145,14 +116,14 @@ ], "foreignKeys": [ { - "table": "notification_info", + "table": "cookies", "onDelete": "CASCADE", "onUpdate": "NO ACTION", "columns": [ "userId" ], "referencedColumns": [ - "id" + "cookie_id" ] } ] @@ -161,7 +132,7 @@ "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, \"d0911840f629ef359aaf8ac8635dc571\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"77eff76407f59b690b8877cc22fac42f\")" ] } } \ 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 index 687d0bc1..fe2aa83e 100644 --- a/app/src/schemas/com.pitchedapps.frost.db.FrostPublicDatabase/1.json +++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPublicDatabase/1.json @@ -31,6 +31,7 @@ "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, \"fde868470836ff9230f1d406922d7563\")" -- cgit v1.2.3 From 7f1f2247de1d61354adfd2cec011cc475a20f683 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 00:12:24 -0500 Subject: Migrate to dao and add filter to title --- .../com/pitchedapps/frost/db/NotificationDbTest.kt | 51 +++++++++++++++++---- .../com/pitchedapps/frost/db/NotificationDb.kt | 26 ++++++----- .../frost/services/FrostNotifications.kt | 52 +++++++++++++--------- .../frost/services/NotificationService.kt | 4 +- 4 files changed, 91 insertions(+), 42 deletions(-) diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt index 2e9f1875..176d0d3a 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt @@ -1,12 +1,12 @@ package com.pitchedapps.frost.db -import android.database.sqlite.SQLiteConstraintException 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.assertFailsWith +import kotlin.test.assertFalse import kotlin.test.assertTrue class NotificationDbTest : BaseDbTest() { @@ -38,6 +38,40 @@ class NotificationDbTest : BaseDbTest() { } } + @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().insertCookie(cookie1) + db.cookieDao().insertCookie(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 */ @@ -50,8 +84,8 @@ class NotificationDbTest : BaseDbTest() { val notifs2 = notifs1.map { it.copy(data = cookie2) } db.cookieDao().insertCookie(cookie1) db.cookieDao().insertCookie(cookie2) - dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs1) - dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs2) + assertTrue(dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs1), "Notif1 save failed") + assertTrue(dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs2), "Notif2 save failed") } } @@ -84,10 +118,11 @@ class NotificationDbTest : BaseDbTest() { @Test fun insertionWithInvalidCookies() { - assertFailsWith(SQLiteConstraintException::class) { - runBlocking { - dao.saveNotifications(NOTIF_CHANNEL_GENERAL, listOf(notifContent(1L, cookie(2L)))) - } + runBlocking { + assertFalse( + dao.saveNotifications(NOTIF_CHANNEL_GENERAL, listOf(notifContent(1L, cookie(2L)))), + "Notif save should not have passed without relevant cookie entries" + ) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt index 9622ec47..60ae2ae7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt @@ -136,9 +136,19 @@ suspend fun NotificationDao.selectNotifications(userId: Long, type: String): Lis _selectNotifications(userId, type).map { it.toNotifContent() } } -suspend fun NotificationDao.saveNotifications(type: String, notifs: List) { - withContext(Dispatchers.IO) { - _saveNotifications(type, notifs) +/** + * Returns true if successful, given that there are constraints to the insertion + */ +suspend fun NotificationDao.saveNotifications(type: String, notifs: List): Boolean { + if (notifs.isEmpty()) return true + return withContext(Dispatchers.IO) { + try { + _saveNotifications(type, notifs) + true + } catch (e: Exception) { + L.e(e) { "Notif save failed" } + false + } } } @@ -175,12 +185,4 @@ data class NotificationModel( 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() - } -} + ?: NotificationModel(id = id) \ No newline at end of file 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 7da3c128..eb81ff04 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt @@ -32,9 +32,11 @@ import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R import com.pitchedapps.frost.activities.FrostWebActivity import com.pitchedapps.frost.db.CookieEntity -import com.pitchedapps.frost.db.CookieModel +import com.pitchedapps.frost.db.FrostDatabase import com.pitchedapps.frost.db.NotificationModel import com.pitchedapps.frost.db.lastNotificationTime +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 @@ -65,8 +67,8 @@ enum class NotificationType( private val overlayContext: OverlayContext, private val fbItem: FbItem, private val parser: FrostParser, + // Legacy; remove with dbflow private val getTime: (notif: NotificationModel) -> Long, - private val putTime: (notif: NotificationModel, time: Long) -> NotificationModel, private val ringtone: () -> String ) { @@ -76,7 +78,6 @@ enum class NotificationType( FbItem.NOTIFICATIONS, NotifParser, NotificationModel::epoch, - { notif, time -> notif.copy(epoch = time) }, Prefs::notificationRingtone ) { @@ -90,7 +91,6 @@ enum class NotificationType( FbItem.MESSAGES, MessageParser, NotificationModel::epochIm, - { notif, time -> notif.copy(epochIm = time) }, Prefs::messageRingtone ); @@ -117,7 +117,8 @@ enum class NotificationType( * Returns the number of notifications generated, * or -1 if an error occurred */ - fun fetch(context: Context, data: CookieEntity): Int { + suspend fun fetch(context: Context, data: CookieEntity): Int { + val notifDao = FrostDatabase.get().notifDao() val response = try { parser.parse(data.cookie) } catch (ignored: Exception) { @@ -128,35 +129,44 @@ enum class NotificationType( return -1 } val notifContents = response.data.getUnreadNotifications(data).filter { notif -> - val text = notif.text - Prefs.notificationKeywords.none { text.contains(it, true) } + val inText = notif.text.let { text -> + Prefs.notificationKeywords.none { text.contains(it, true) } + } + val inTitle = notif.title?.let { title -> + Prefs.notificationKeywords.none { title.contains(it, true) } + } ?: false + inText || inTitle } 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).takeIf { it != -1L } ?: getTime(lastNotificationTime(userId)) L.v { "Notif $name prev epoch $prevLatestEpoch" } - var newLatestEpoch = prevLatestEpoch - val notifs = mutableListOf() - 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 { "Notif $name new epoch ${newNotifContents.map { it.timestamp }.max()}" } + + val notifs = newNotifContents.map { createNotification(context, it) } + + notifDao.saveNotifications(channelId, newNotifContents) + 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 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 088d8e0a..e1db5fa6 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -23,6 +23,7 @@ import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R import com.pitchedapps.frost.db.CookieDao import com.pitchedapps.frost.db.CookieEntity +import com.pitchedapps.frost.db.NotificationDao import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.frostEvent @@ -44,6 +45,7 @@ import org.koin.android.ext.android.inject class NotificationService : BaseJobService() { val cookieDao: CookieDao by inject() + val notifDao: NotificationDao by inject() override fun onStopJob(params: JobParameters?): Boolean { super.onStopJob(params) @@ -110,7 +112,7 @@ class NotificationService : BaseJobService() { * 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: CookieEntity): 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) -- cgit v1.2.3 From e96160d7f7cf45cdb8673928ac9e2fbca63002bd Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 00:16:22 -0500 Subject: Do not send notifications if db save fails --- .../kotlin/com/pitchedapps/frost/services/FrostNotifications.kt | 9 ++++++--- .../kotlin/com/pitchedapps/frost/services/NotificationService.kt | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) 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 eb81ff04..14b6ec39 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt @@ -155,11 +155,14 @@ enum class NotificationType( return 0 } - L.d { "Notif $name new epoch ${newNotifContents.map { it.timestamp }.max()}" } + L.d { "${newNotifContents.size} new notifs found for $name" } - val notifs = newNotifContents.map { createNotification(context, it) } + if (!notifDao.saveNotifications(channelId, newNotifContents)) { + L.d { "Skip notifs for $name as saving failed" } + return 0 + } - notifDao.saveNotifications(channelId, newNotifContents) + val notifs = newNotifContents.map { createNotification(context, it) } frostEvent("Notifications", "Type" to name, "Count" to notifs.size) if (notifs.size > 1) 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 e1db5fa6..8b100c19 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -45,7 +45,6 @@ import org.koin.android.ext.android.inject class NotificationService : BaseJobService() { val cookieDao: CookieDao by inject() - val notifDao: NotificationDao by inject() override fun onStopJob(params: JobParameters?): Boolean { super.onStopJob(params) -- cgit v1.2.3 From cf5fd28ec74069746b49757b6a95a36851a56105 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 00:22:15 -0500 Subject: Move legacy notif model to internal call --- .../main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt | 14 ++++++++++---- .../com/pitchedapps/frost/services/FrostNotifications.kt | 9 +-------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt index 60ae2ae7..7f41fbf8 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt @@ -26,6 +26,8 @@ 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 @@ -33,10 +35,8 @@ 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 @@ -153,7 +153,13 @@ suspend fun NotificationDao.saveNotifications(type: String, notifs: List it.epoch + NOTIF_CHANNEL_MESSAGES -> it.epochIm + else -> -1L + } + } } /** @@ -183,6 +189,6 @@ data class NotificationModel( var epochIm: Long = -1L ) : BaseModel() -fun lastNotificationTime(id: Long): NotificationModel = +private fun lastNotificationTime(id: Long): NotificationModel = (select from NotificationModel::class where (NotificationModel_Table.id eq id)).querySingle() ?: NotificationModel(id = id) \ No newline at end of file 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 14b6ec39..631912a3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt @@ -33,8 +33,6 @@ import com.pitchedapps.frost.R import com.pitchedapps.frost.activities.FrostWebActivity import com.pitchedapps.frost.db.CookieEntity import com.pitchedapps.frost.db.FrostDatabase -import com.pitchedapps.frost.db.NotificationModel -import com.pitchedapps.frost.db.lastNotificationTime import com.pitchedapps.frost.db.latestEpoch import com.pitchedapps.frost.db.saveNotifications import com.pitchedapps.frost.enums.OverlayContext @@ -67,8 +65,6 @@ enum class NotificationType( private val overlayContext: OverlayContext, private val fbItem: FbItem, private val parser: FrostParser, - // Legacy; remove with dbflow - private val getTime: (notif: NotificationModel) -> Long, private val ringtone: () -> String ) { @@ -77,7 +73,6 @@ enum class NotificationType( OverlayContext.NOTIFICATION, FbItem.NOTIFICATIONS, NotifParser, - NotificationModel::epoch, Prefs::notificationRingtone ) { @@ -90,7 +85,6 @@ enum class NotificationType( OverlayContext.MESSAGE, FbItem.MESSAGES, MessageParser, - NotificationModel::epochIm, Prefs::messageRingtone ); @@ -140,8 +134,7 @@ enum class NotificationType( if (notifContents.isEmpty()) return 0 val userId = data.id // Legacy, remove with dbflow - val prevLatestEpoch = - notifDao.latestEpoch(userId, channelId).takeIf { it != -1L } ?: getTime(lastNotificationTime(userId)) + val prevLatestEpoch = notifDao.latestEpoch(userId, channelId) L.v { "Notif $name prev epoch $prevLatestEpoch" } if (prevLatestEpoch == -1L && !BuildConfig.DEBUG) { L.d { "Skipping first notification fetch" } -- cgit v1.2.3 From d7ee076bdc1a179a0fdb45b812642b8656d74415 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 00:32:17 -0500 Subject: Allow migration for fbtabs --- .../kotlin/com/pitchedapps/frost/StartActivity.kt | 12 ++++++--- .../frost/activities/TabCustomizerActivity.kt | 30 +++++++++++++++------- .../kotlin/com/pitchedapps/frost/db/FbTabsDb.kt | 17 +----------- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt index e81df3fa..3b7418e1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt @@ -35,7 +35,9 @@ import com.pitchedapps.frost.activities.SelectorActivity import com.pitchedapps.frost.db.CookieDao import com.pitchedapps.frost.db.CookieEntity import com.pitchedapps.frost.db.CookieModel +import com.pitchedapps.frost.db.FbTabDao import com.pitchedapps.frost.db.FbTabModel +import com.pitchedapps.frost.db.save import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.utils.EXTRA_COOKIES import com.pitchedapps.frost.utils.L @@ -56,6 +58,7 @@ import java.util.ArrayList class StartActivity : KauBaseActivity() { private val cookieDao: CookieDao by inject() + private val tabDao: FbTabDao by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -98,13 +101,14 @@ class StartActivity : KauBaseActivity() { /** * 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() - cookieDao.insertCookies(cookies.map { CookieEntity(it.id, it.name, it.cookie) }) - // TODO - val tabs = (select from FbTabModel::class).queryList() + val cookies = (select from CookieModel::class).queryList().map { CookieEntity(it.id, it.name, it.cookie) } + cookieDao.insertCookies(cookies) + val tabs = (select from FbTabModel::class).queryList().map(FbTabModel::tab) + tabDao.save(tabs) } private fun showInvalidWebView() = 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 73e6419e..a5dceb3a 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,18 @@ 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.db.FbTabDao import com.pitchedapps.frost.db.TAB_COUNT -import com.pitchedapps.frost.db.loadFbTabs import com.pitchedapps.frost.db.save +import com.pitchedapps.frost.db.selectAll import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.iitems.TabIItem 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 +53,8 @@ import java.util.Collections */ class TabCustomizerActivity : BaseActivity() { + private val tabDao: FbTabDao by inject() + private val adapter = FastItemAdapter() private val wobble = lazyContext { AnimationUtils.loadAnimation(it, R.anim.rotate_delta) } @@ -65,12 +72,14 @@ 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 = tabDao.selectAll().toMutableList() + val remaining = FbItem.values().filter { it.name[0] != '_' }.toMutableList() + remaining.removeAll(tabs) + tabs.addAll(remaining) + adapter.add(tabs.map(::TabIItem)) + } - adapter.add(tabs.map(::TabIItem)) bindSwapper(adapter, tab_recycler) adapter.withOnClickListener { view, _, _, _ -> view!!.wobble(); true } @@ -80,9 +89,12 @@ class TabCustomizerActivity : BaseActivity() { 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) + tabDao.save(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/db/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt index 582d57fb..c2bb0837 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt @@ -103,19 +103,4 @@ 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 { - val tabs: List? = (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.save() { - database().beginTransactionAsync(mapIndexed(::FbTabModel).fastSave().build()).execute() -} +data class FbTabModel(@PrimaryKey var position: Int = -1, var tab: FbItem = FbItem.FEED) : BaseModel() \ No newline at end of file -- cgit v1.2.3 From f717953debf1cd023b2f612e0ea9b2eede4c37c3 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 00:36:11 -0500 Subject: Delete unused dbutils --- .../main/kotlin/com/pitchedapps/frost/FrostApp.kt | 2 +- .../kotlin/com/pitchedapps/frost/db/DbUtils.kt | 38 ---------------------- 2 files changed, 1 insertion(+), 39 deletions(-) delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/db/DbUtils.kt diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt index 7669ef46..41c6ff4b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt @@ -83,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 } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/DbUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/DbUtils.kt deleted file mode 100644 index 03aac059..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/DbUtils.kt +++ /dev/null @@ -1,38 +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 . - */ -package com.pitchedapps.frost.db - -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 List.replace(dbName: String) { - L.d { "Replacing $dbName.db" } - DbUtils.db(dbName).reset() - FastStoreModelTransaction.saveBuilder(FlowManager.getModelAdapter(T::class.java)).addAll(this).build() -} -- cgit v1.2.3 From eabc8e3393cbd6b3dba5c70a5920075b8158d84e Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 03:12:27 -0500 Subject: Add cache entityt --- .../kotlin/com/pitchedapps/frost/db/CacheDb.kt | 56 ++++++++++++++++++++++ .../kotlin/com/pitchedapps/frost/db/Database.kt | 3 +- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt 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..bd6bff4b --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt @@ -0,0 +1,56 @@ +/* + * 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 . + */ +package com.pitchedapps.frost.db + +import android.os.Parcelable +import androidx.room.Dao +import androidx.room.Entity +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import kotlinx.android.parcel.Parcelize + +/** + * Created by Allan Wang on 2017-05-30. + */ + +/** + * Generic cache to store serialized content + */ +@Entity(tableName = "frost_cache") +@Parcelize +data class CacheEntity( + @androidx.room.PrimaryKey + val id: String, + val lastUpdated: Long, + val contents: String +) : Parcelable + +@Dao +interface CacheDao { + + @Query("SELECT * FROM frost_cache WHERE id = :id") + suspend fun selectById(id: Long): CacheEntity? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertCache(cache: CacheEntity) + + @Query("DELETE FROM frost_cache WHERE id = :id") + suspend fun deleteById(id: Long) +} + +suspend fun CacheDao.save(id: String, contents: String) = insertCache(CacheEntity(id, System.currentTimeMillis(), contents)) \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt index 1ce9e7c2..b83fce52 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt @@ -11,10 +11,11 @@ import org.koin.standalone.StandAloneContext interface FrostPrivateDao { fun cookieDao(): CookieDao fun notifDao(): NotificationDao + fun cacheDao(): CacheDao } @Database( - entities = [CookieEntity::class, NotificationEntity::class], + entities = [CookieEntity::class, NotificationEntity::class, CacheEntity::class], version = 1, exportSchema = true ) -- cgit v1.2.3 From f1878133d8af686ce8c27acffe28f26e9dda5165 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 04:00:20 -0500 Subject: Add koin test --- app/build.gradle | 1 + .../com/pitchedapps/frost/db/DatabaseTest.kt | 38 ++++++++++++++++++++++ .../kotlin/com/pitchedapps/frost/db/CacheDb.kt | 3 +- .../kotlin/com/pitchedapps/frost/db/Database.kt | 2 ++ .../kotlin/com/pitchedapps/frost/db/FbTabsDb.kt | 5 --- .../1.json | 36 ++++++++++++++++++-- gradle.properties | 2 +- 7 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/pitchedapps/frost/db/DatabaseTest.kt diff --git a/app/build.gradle b/app/build.gradle index d5dcf87e..74e8015c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -178,6 +178,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}" 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..1f1a201b --- /dev/null +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/DatabaseTest.kt @@ -0,0 +1,38 @@ +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 hasKoin() = hasKoin(T::class) + + fun hasKoin(klazz: KClass): Boolean = + try { + get(clazz = klazz) + true + } catch (e: NoBeanDefFoundException) { + false + } + + /** + * Database and all daos should be loaded as components + */ + @Test + fun testKoins() { + hasKoin() + 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") + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt index bd6bff4b..4d6bc938 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt @@ -53,4 +53,5 @@ interface CacheDao { suspend fun deleteById(id: Long) } -suspend fun CacheDao.save(id: String, contents: String) = insertCache(CacheEntity(id, System.currentTimeMillis(), contents)) \ No newline at end of file +suspend fun CacheDao.save(id: String, contents: String) = + insertCache(CacheEntity(id, System.currentTimeMillis(), contents)) \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt index b83fce52..29296494 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt @@ -71,6 +71,8 @@ class FrostDatabase(private val privateDb: FrostPrivateDatabase, private val pub single { create(context) } single { get().cookieDao() } single { get().tabDao() } + single { get().cacheDao() } + single { get().notifDao() } } /** diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt index c2bb0837..f4e74509 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt @@ -24,14 +24,9 @@ import androidx.room.Query import androidx.room.Transaction 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 import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json index c382bce7..72b86db3 100644 --- a/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json +++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "77eff76407f59b690b8877cc22fac42f", + "identityHash": "099ffebd0f0fe80199c0f6413a549ebb", "entities": [ { "tableName": "cookies", @@ -127,12 +127,44 @@ ] } ] + }, + { + "tableName": "frost_cache", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, `contents` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contents", + "columnName": "contents", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "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, \"77eff76407f59b690b8877cc22fac42f\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"099ffebd0f0fe80199c0f6413a549ebb\")" ] } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 98f29dbe..146591bb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,7 +18,7 @@ KAU=4.0.0-alpha02 KOTLIN=1.3.21 # https://mvnrepository.com/artifact/com.android.tools.build/gradle?repo=google -ANDROID_GRADLE=3.2.1 +ANDROID_GRADLE=3.3.2 # https://github.com/diffplug/spotless/blob/master/plugin-gradle/CHANGES.md SPOTLESS=3.17.0 -- cgit v1.2.3 From 12f491737ec2e2d774a816e84170ff352d1b6cd6 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 14:59:40 -0500 Subject: Add cache test --- .../kotlin/com/pitchedapps/frost/db/CacheDbTest.kt | 32 ++++++++++++++++++ .../kotlin/com/pitchedapps/frost/db/CacheDb.kt | 38 +++++++++++++++++----- .../com/pitchedapps/frost/db/NotificationDb.kt | 2 +- .../1.json | 29 ++++++++++++++--- 4 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt 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..780bbd3e --- /dev/null +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt @@ -0,0 +1,32 @@ +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.insertCookie(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})" + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt index 4d6bc938..8c3c9c6b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt @@ -19,9 +19,11 @@ 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 /** @@ -31,11 +33,20 @@ import kotlinx.android.parcel.Parcelize /** * Generic cache to store serialized content */ -@Entity(tableName = "frost_cache") +@Entity( + tableName = "frost_cache", + primaryKeys = ["id", "type"], + foreignKeys = [ForeignKey( + entity = CookieEntity::class, + parentColumns = ["cookie_id"], + childColumns = ["id"], + onDelete = ForeignKey.CASCADE + )] +) @Parcelize data class CacheEntity( - @androidx.room.PrimaryKey - val id: String, + val id: Long, + val type: String, val lastUpdated: Long, val contents: String ) : Parcelable @@ -43,15 +54,24 @@ data class CacheEntity( @Dao interface CacheDao { - @Query("SELECT * FROM frost_cache WHERE id = :id") - suspend fun selectById(id: Long): CacheEntity? + @Query("SELECT * FROM frost_cache WHERE id = :id AND type = :type") + suspend fun select(id: Long, type: String): CacheEntity? @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertCache(cache: CacheEntity) - @Query("DELETE FROM frost_cache WHERE id = :id") - suspend fun deleteById(id: Long) + @Query("DELETE FROM frost_cache WHERE id = :id AND type = :type") + suspend fun delete(id: Long, type: String) } -suspend fun CacheDao.save(id: String, contents: String) = - insertCache(CacheEntity(id, System.currentTimeMillis(), contents)) \ No newline at end of file +/** + * Returns true if successful, given that there are constraints to the insertion + */ +suspend fun CacheDao.save(id: Long, type: String, contents: String): Boolean = + try { + insertCache(CacheEntity(id, type, System.currentTimeMillis(), contents)) + true + } catch (e: Exception) { + L.e(e) { "Cache save failed for $type" } + false + } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt index 7f41fbf8..d2771754 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt @@ -146,7 +146,7 @@ suspend fun NotificationDao.saveNotifications(type: String, notifs: List Date: Thu, 7 Mar 2019 15:26:35 -0500 Subject: Optimize imports --- app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt | 2 +- app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt | 2 +- app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt | 3 +-- app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt | 2 -- .../main/kotlin/com/pitchedapps/frost/facebook/parsers/FrostParser.kt | 1 - .../kotlin/com/pitchedapps/frost/facebook/parsers/MessageParser.kt | 1 - .../main/kotlin/com/pitchedapps/frost/facebook/parsers/NotifParser.kt | 1 - .../main/kotlin/com/pitchedapps/frost/services/NotificationService.kt | 1 - app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt | 1 - app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt | 1 - app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt | 1 - .../test/kotlin/com/pitchedapps/frost/debugger/OfflineWebsiteTest.kt | 2 +- 12 files changed, 4 insertions(+), 14 deletions(-) diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt index 20592347..5ec771f5 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt @@ -6,7 +6,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNull class CookieDbTest : BaseDbTest() { - + private val dao get() = db.cookieDao() @Test diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt index 91a0bf9a..752112f9 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt @@ -7,7 +7,7 @@ import kotlin.test.Test import kotlin.test.assertEquals class FbTabsDbTest : BaseDbTest() { - + private val dao get() = db.tabDao() /** 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 a1dba417..75c9537b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt @@ -86,8 +86,7 @@ class MainActivity : BaseMainActivity() { (tab.customView as BadgedIcon).badgeText = null } }) - headerBadgeChannel.subscribeDuringJob(this, Dispatchers.IO) { - html -> + headerBadgeChannel.subscribeDuringJob(this, Dispatchers.IO) { html -> try { val doc = Jsoup.parse(html) if (doc.select("[data-sigil=count]").isEmpty()) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt index d5347f18..c6c983fb 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt @@ -82,6 +82,4 @@ data class CookieModel(@PrimaryKey var id: Long = -1L, var name: String? = null, BaseModel(), Parcelable { override fun toString(): String = "CookieModel(${hashCode()})" - - fun toSensitiveString(): String = "CookieModel(id=$id, name=$name, cookie=$cookie)" } \ No newline at end of file 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 bf89a103..4c494e0b 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 @@ -17,7 +17,6 @@ package com.pitchedapps.frost.facebook.parsers import com.pitchedapps.frost.db.CookieEntity -import com.pitchedapps.frost.db.CookieModel import com.pitchedapps.frost.facebook.FB_CSS_URL_MATCHER import com.pitchedapps.frost.facebook.formattedFbUrl import com.pitchedapps.frost.facebook.get 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 866cab10..80ed8ee8 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 @@ -17,7 +17,6 @@ package com.pitchedapps.frost.facebook.parsers import com.pitchedapps.frost.db.CookieEntity -import com.pitchedapps.frost.db.CookieModel import com.pitchedapps.frost.facebook.FB_EPOCH_MATCHER import com.pitchedapps.frost.facebook.FB_MESSAGE_NOTIF_ID_MATCHER import com.pitchedapps.frost.facebook.FbItem 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 3449ac48..199fc685 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 @@ -17,7 +17,6 @@ package com.pitchedapps.frost.facebook.parsers import com.pitchedapps.frost.db.CookieEntity -import com.pitchedapps.frost.db.CookieModel import com.pitchedapps.frost.facebook.FB_EPOCH_MATCHER import com.pitchedapps.frost.facebook.FB_NOTIF_ID_MATCHER import com.pitchedapps.frost.facebook.FbItem 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 8b100c19..2e994577 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -23,7 +23,6 @@ import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R import com.pitchedapps.frost.db.CookieDao import com.pitchedapps.frost.db.CookieEntity -import com.pitchedapps.frost.db.NotificationDao import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.frostEvent 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 288e2b40..75b80ae1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt @@ -40,7 +40,6 @@ 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.CookieModel_Table.cookie import com.pitchedapps.frost.db.FrostDatabase import com.pitchedapps.frost.db.currentCookie import com.pitchedapps.frost.utils.L 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 4df84b63..0d980ba0 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt @@ -22,7 +22,6 @@ import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.contracts.MainActivityContract import com.pitchedapps.frost.contracts.VideoViewHolder import com.pitchedapps.frost.db.CookieEntity -import com.pitchedapps.frost.db.CookieModel import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs 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 07aafaf0..4c7572fb 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt @@ -30,7 +30,6 @@ import ca.allanwang.kau.utils.fadeIn import ca.allanwang.kau.utils.isVisible import ca.allanwang.kau.utils.launchMain import com.pitchedapps.frost.db.CookieEntity -import com.pitchedapps.frost.db.CookieModel import com.pitchedapps.frost.facebook.FB_LOGIN_URL import com.pitchedapps.frost.facebook.FB_USER_MATCHER import com.pitchedapps.frost.facebook.FbCookie 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 -- cgit v1.2.3 From e617c95e2b4acfcfbeb1a72a658f19e69eaa3d6c Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 17:28:32 -0500 Subject: Rename some methods --- .../kotlin/com/pitchedapps/frost/db/CacheDbTest.kt | 2 +- .../com/pitchedapps/frost/db/CookieDbTest.kt | 12 +- .../com/pitchedapps/frost/db/NotificationDbTest.kt | 14 +- .../kotlin/com/pitchedapps/frost/StartActivity.kt | 6 +- .../pitchedapps/frost/activities/LoginActivity.kt | 2 +- .../kotlin/com/pitchedapps/frost/db/CookiesDb.kt | 4 +- .../kotlin/com/pitchedapps/frost/db/Database.kt | 5 +- .../com/pitchedapps/frost/facebook/FbCookie.kt | 2 +- .../main/kotlin/com/pitchedapps/frost/utils/L.kt | 6 + .../1.json | 189 --------------------- .../1.json | 40 ----- 11 files changed, 32 insertions(+), 250 deletions(-) delete mode 100644 app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json delete mode 100644 app/src/schemas/com.pitchedapps.frost.db.FrostPublicDatabase/1.json diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt index 780bbd3e..1fe7bbc4 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt @@ -19,7 +19,7 @@ class CacheDbTest : BaseDbTest() { val type = "test" val content = "long test".repeat(10000) runBlocking { - cookieDao.insertCookie(cookie) + 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") diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt index 5ec771f5..122e3205 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt @@ -13,7 +13,7 @@ class CookieDbTest : BaseDbTest() { fun basicCookie() { val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") runBlocking { - dao.insertCookie(cookie) + dao.save(cookie) val cookies = dao.selectAll() assertEquals(listOf(cookie), cookies, "Cookie mismatch") } @@ -24,7 +24,7 @@ class CookieDbTest : BaseDbTest() { val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") runBlocking { - dao.insertCookie(cookie) + dao.save(cookie) dao.deleteById(cookie.id + 1) assertEquals( listOf(cookie), @@ -40,15 +40,15 @@ class CookieDbTest : BaseDbTest() { fun insertReplaceCookie() { val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") runBlocking { - dao.insertCookie(cookie) + dao.save(cookie) assertEquals(listOf(cookie), dao.selectAll(), "Cookie insertion failed") - dao.insertCookie(cookie.copy(name = "testName2")) + dao.save(cookie.copy(name = "testName2")) assertEquals( listOf(cookie.copy(name = "testName2")), dao.selectAll(), "Cookie replacement failed" ) - dao.insertCookie(cookie.copy(id = 123L)) + dao.save(cookie.copy(id = 123L)) assertEquals( setOf(cookie.copy(id = 123L), cookie.copy(name = "testName2")), dao.selectAll().toSet(), @@ -61,7 +61,7 @@ class CookieDbTest : BaseDbTest() { fun selectCookie() { val cookie = CookieEntity(id = 1234L, name = "testName", cookie = "testCookie") runBlocking { - dao.insertCookie(cookie) + 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/NotificationDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt index 176d0d3a..25c29db4 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt @@ -31,7 +31,7 @@ class NotificationDbTest : BaseDbTest() { // Unique unsorted ids val notifs = listOf(0L, 4L, 2L, 6L, 99L, 3L).map { notifContent(it, cookie) } runBlocking { - db.cookieDao().insertCookie(cookie) + 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") @@ -45,8 +45,8 @@ class NotificationDbTest : BaseDbTest() { val cookie2 = cookie(12L) val notifs1 = (0L..2L).map { notifContent(it, cookie1) } val notifs2 = (5L..10L).map { notifContent(it, cookie2) } - db.cookieDao().insertCookie(cookie1) - db.cookieDao().insertCookie(cookie2) + db.cookieDao().save(cookie1) + db.cookieDao().save(cookie2) dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs1) dao.saveNotifications(NOTIF_CHANNEL_MESSAGES, notifs2) assertEquals( @@ -82,8 +82,8 @@ class NotificationDbTest : BaseDbTest() { val cookie2 = cookie(12L) val notifs1 = (0L..2L).map { notifContent(it, cookie1) } val notifs2 = notifs1.map { it.copy(data = cookie2) } - db.cookieDao().insertCookie(cookie1) - db.cookieDao().insertCookie(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") } @@ -95,7 +95,7 @@ class NotificationDbTest : BaseDbTest() { // Unique unsorted ids val notifs = listOf(0L, 4L, 2L, 6L, 99L, 3L).map { notifContent(it, cookie) } runBlocking { - db.cookieDao().insertCookie(cookie) + db.cookieDao().save(cookie) dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs) db.cookieDao().deleteById(cookie.id) val dbNotifs = dao.selectNotifications(cookie.id, NOTIF_CHANNEL_GENERAL) @@ -110,7 +110,7 @@ class NotificationDbTest : BaseDbTest() { 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().insertCookie(cookie) + db.cookieDao().save(cookie) dao.saveNotifications(NOTIF_CHANNEL_GENERAL, notifs) assertEquals(99L, dao.latestEpoch(cookie.id, NOTIF_CHANNEL_GENERAL), "Latest epoch failed") } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt index 3b7418e1..24e9c548 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt @@ -38,6 +38,7 @@ import com.pitchedapps.frost.db.CookieModel import com.pitchedapps.frost.db.FbTabDao import com.pitchedapps.frost.db.FbTabModel import com.pitchedapps.frost.db.save +import com.pitchedapps.frost.db.selectAll import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.utils.EXTRA_COOKIES import com.pitchedapps.frost.utils.L @@ -94,6 +95,7 @@ class StartActivity : KauBaseActivity() { }) } } catch (e: Exception) { + L._e(e) { "Load start failed" } showInvalidWebView() } } @@ -106,9 +108,11 @@ class StartActivity : KauBaseActivity() { 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) } - cookieDao.insertCookies(cookies) + cookieDao.save(cookies) val tabs = (select from FbTabModel::class).queryList().map(FbTabModel::tab) tabDao.save(tabs) + L._d { "Migrated cookies ${cookieDao.selectAll()}" } + L._d { "Migrated tabs ${tabDao.selectAll()}" } } private fun showInvalidWebView() = 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 27dbc37a..e5a50543 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt @@ -184,7 +184,7 @@ class LoginActivity : BaseActivity() { } if (cookie.name?.isNotBlank() == false && result != cookie.name) { - cookieDao.insertCookie(cookie.copy(name = result)) + cookieDao.save(cookie.copy(name = result)) } cookie.name ?: "" diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt index c6c983fb..7e929370 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt @@ -59,10 +59,10 @@ interface CookieDao { suspend fun selectById(id: Long): CookieEntity? @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertCookie(cookie: CookieEntity) + suspend fun save(cookie: CookieEntity) @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertCookies(cookies: List) + suspend fun save(cookies: List) @Query("DELETE FROM cookies WHERE cookie_id = :id") suspend fun deleteById(id: Long) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt index 29296494..ae96b696 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt @@ -5,6 +5,7 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters +import org.koin.core.Koin import org.koin.dsl.module.module import org.koin.standalone.StandAloneContext @@ -59,11 +60,11 @@ class FrostDatabase(private val privateDb: FrostPrivateDatabase, private val pub val privateDb = Room.databaseBuilder( context, FrostPrivateDatabase::class.java, FrostPrivateDatabase.DATABASE_NAME - ).build() + ).fallbackToDestructiveMigration().build() val publicDb = Room.databaseBuilder( context, FrostPublicDatabase::class.java, FrostPublicDatabase.DATABASE_NAME - ).build() + ).fallbackToDestructiveMigration().build() return FrostDatabase(privateDb, publicDb) } 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 02718b78..6afbea4b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt @@ -85,7 +85,7 @@ object FbCookie { Prefs.userId = id CookieManager.getInstance().flush() val cookie = CookieEntity(Prefs.userId, null, webCookie) - cookieDao.insertCookie(cookie) + cookieDao.save(cookie) } suspend fun reset() { 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..7d2e0e08 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt @@ -20,6 +20,7 @@ import android.util.Log import ca.allanwang.kau.logging.KauLogger import com.bugsnag.android.Bugsnag import com.pitchedapps.frost.BuildConfig +import java.lang.Exception /** * Created by Allan Wang on 2017-05-28. @@ -50,6 +51,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/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json deleted file mode 100644 index 60d5cddd..00000000 --- a/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 1, - "identityHash": "0a9d994786b7e07fea95c11d9210ce0e", - "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, 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 - } - ], - "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, \"0a9d994786b7e07fea95c11d9210ce0e\")" - ] - } -} \ 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 deleted file mode 100644 index fe2aa83e..00000000 --- a/app/src/schemas/com.pitchedapps.frost.db.FrostPublicDatabase/1.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 1, - "identityHash": "fde868470836ff9230f1d406922d7563", - "entities": [ - { - "tableName": "tabs", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`position` INTEGER NOT NULL, `tab` TEXT NOT NULL, PRIMARY KEY(`position`))", - "fields": [ - { - "fieldPath": "position", - "columnName": "position", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "tab", - "columnName": "tab", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "position" - ], - "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, \"fde868470836ff9230f1d406922d7563\")" - ] - } -} \ No newline at end of file -- cgit v1.2.3 From b769e36802c15f521665a353e8ed5e637efeef1f Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 17:29:53 -0500 Subject: Disable fallback --- app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt index ae96b696..5fc986e3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt @@ -60,11 +60,11 @@ class FrostDatabase(private val privateDb: FrostPrivateDatabase, private val pub val privateDb = Room.databaseBuilder( context, FrostPrivateDatabase::class.java, FrostPrivateDatabase.DATABASE_NAME - ).fallbackToDestructiveMigration().build() + ).build() val publicDb = Room.databaseBuilder( context, FrostPublicDatabase::class.java, FrostPublicDatabase.DATABASE_NAME - ).fallbackToDestructiveMigration().build() + ).build() return FrostDatabase(privateDb, publicDb) } -- cgit v1.2.3 From adf9f2f6f23de17aae51435f792de16a0680f286 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 18:15:46 -0500 Subject: Rebind main activity logic --- .../frost/activities/BaseMainActivity.kt | 72 ++++++-- .../pitchedapps/frost/activities/MainActivity.kt | 20 +-- .../1.json | 189 +++++++++++++++++++++ .../1.json | 40 +++++ 4 files changed, 291 insertions(+), 30 deletions(-) create mode 100644 app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json create mode 100644 app/src/schemas/com.pitchedapps.frost.db.FrostPublicDatabase/1.json 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 4669418d..ca30401a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt @@ -123,7 +123,10 @@ 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() @@ -136,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 @@ -156,14 +161,14 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, background(viewPager) } setSupportActionBar(toolbar) - launch { - adapter = SectionsPagerAdapter(tabDao.selectAll()) - viewPager.adapter = adapter - viewPager.offscreenPageLimit = TAB_COUNT - } + 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(tabDao.selectAll()) + } controlWebview = WebView(this) if (BuildConfig.VERSION_CODE > Prefs.versionCode) { Prefs.prevVersionCode = Prefs.versionCode @@ -462,16 +467,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() { @@ -526,9 +527,47 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, runOnUiThread { adapter.reloadFragment(fragment) } } - inner class SectionsPagerAdapter(val pages: List) : FragmentPagerAdapter(supportFragmentManager) { + inner class SectionsPagerAdapter : FragmentPagerAdapter(supportFragmentManager) { + + private val pages: MutableList = mutableListOf() - val forcedFallbacks = mutableSetOf() + private val forcedFallbacks = mutableSetOf() + + /** + * Update page list and prompt reload + */ + fun setPages(pages: List) { + 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.post { + if (!fragmentChannel.isClosedForSend) + fragmentChannel.offer(0) + } //trigger hook so title is set + } + + fun saveInstanceState(outState: Bundle) { + outState.putStringArrayList(STATE_FORCE_FALLBACK, ArrayList(forcedFallbacks)) + } + + fun restoreInstanceState(savedInstanceState: Bundle) { + forcedFallbacks.clear() + forcedFallbacks.addAll( + savedInstanceState.getStringArrayList(STATE_FORCE_FALLBACK) + ?: emptyList() + ) + } fun reloadFragment(fragment: BaseFragment) { if (fragment is WebFragment) return @@ -567,4 +606,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/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(10) override val headerBadgeChannel = BroadcastChannel(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/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..60d5cddd --- /dev/null +++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json @@ -0,0 +1,189 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "0a9d994786b7e07fea95c11d9210ce0e", + "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, 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 + } + ], + "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, \"0a9d994786b7e07fea95c11d9210ce0e\")" + ] + } +} \ 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..fe2aa83e --- /dev/null +++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPublicDatabase/1.json @@ -0,0 +1,40 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "fde868470836ff9230f1d406922d7563", + "entities": [ + { + "tableName": "tabs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`position` INTEGER NOT NULL, `tab` TEXT NOT NULL, PRIMARY KEY(`position`))", + "fields": [ + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tab", + "columnName": "tab", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "position" + ], + "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, \"fde868470836ff9230f1d406922d7563\")" + ] + } +} \ No newline at end of file -- cgit v1.2.3 From 7b5df27ea3051676ca29e5960d72a36eebdcde03 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 18:18:10 -0500 Subject: Set page limit based on tab count --- .../kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 ca30401a..1f96f076 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt @@ -71,7 +71,6 @@ import com.pitchedapps.frost.contracts.MainActivityContract import com.pitchedapps.frost.contracts.VideoViewHolder import com.pitchedapps.frost.db.CookieDao import com.pitchedapps.frost.db.FbTabDao -import com.pitchedapps.frost.db.TAB_COUNT import com.pitchedapps.frost.db.currentCookie import com.pitchedapps.frost.db.selectAll import com.pitchedapps.frost.enums.MainActivityLayout @@ -162,12 +161,13 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, } setSupportActionBar(toolbar) 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(tabDao.selectAll()) + val tabs = tabDao.selectAll() + adapter.setPages(tabs) + viewPager.offscreenPageLimit = tabs.size } controlWebview = WebView(this) if (BuildConfig.VERSION_CODE > Prefs.versionCode) { -- cgit v1.2.3 From f038ed790f74d496098575d4e9112f958d9cd6c9 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 18:29:24 -0500 Subject: Fix tab customizer activity --- .../com/pitchedapps/frost/activities/TabCustomizerActivity.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 a5dceb3a..7781e190 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt @@ -40,6 +40,7 @@ import com.pitchedapps.frost.db.save import com.pitchedapps.frost.db.selectAll 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.* @@ -74,15 +75,17 @@ class TabCustomizerActivity : BaseActivity() { launch { val tabs = tabDao.selectAll().toMutableList() + L.d { "Tabs $tabs" } val remaining = FbItem.values().filter { it.name[0] != '_' }.toMutableList() remaining.removeAll(tabs) tabs.addAll(remaining) - adapter.add(tabs.map(::TabIItem)) - } + adapter.set(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) -- cgit v1.2.3 From 9edbab9845b8a1182dee35121a6348537fc657f7 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 18:47:17 -0500 Subject: Delete dbflow data on migration --- app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt index 24e9c548..24b848fe 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt @@ -108,11 +108,17 @@ class StartActivity : KauBaseActivity() { 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) } - cookieDao.save(cookies) + if (cookies.isNotEmpty()) { + cookieDao.save(cookies) + L._d { "Migrated cookies ${cookieDao.selectAll()}" } + } val tabs = (select from FbTabModel::class).queryList().map(FbTabModel::tab) - tabDao.save(tabs) - L._d { "Migrated cookies ${cookieDao.selectAll()}" } - L._d { "Migrated tabs ${tabDao.selectAll()}" } + if (tabs.isNotEmpty()) { + tabDao.save(tabs) + L._d { "Migrated tabs ${tabDao.selectAll()}" } + } + deleteDatabase("Cookies.db") + deleteDatabase("FrostTabs.db") } private fun showInvalidWebView() = -- cgit v1.2.3 From 2056d9cb72ef8088a6993cbbb48d5bbb41ffddf2 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 18:56:42 -0500 Subject: Make cookie name null by default --- .../com/pitchedapps/frost/activities/LoginActivity.kt | 15 ++++++++------- .../main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt | 3 +-- 2 files changed, 9 insertions(+), 9 deletions(-) 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 e5a50543..610b2796 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt @@ -116,7 +116,7 @@ class LoginActivity : BaseActivity() { 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" } @@ -127,7 +127,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) @@ -172,22 +172,23 @@ class LoginActivity : BaseActivity() { } } - private suspend fun loadUsername(cookie: CookieEntity): 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) { + 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/web/LoginWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt index 4c7572fb..4b891800 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt @@ -89,8 +89,7 @@ class LoginWebView @JvmOverloads constructor( 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 - // TODO set name to null? - return CookieEntity(id, "", cookie) + return CookieEntity(id, null, cookie) } override fun onPageCommitVisible(view: WebView, url: String?) { -- cgit v1.2.3 From 0f500024a3b4196623dba9ef75e7f604bec32040 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 19:01:30 -0500 Subject: Move viewpager page setup to adapter --- .../kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 1f96f076..458e97c9 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt @@ -32,6 +32,7 @@ import android.webkit.WebView import android.widget.FrameLayout import androidx.annotation.StringRes import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.view.size import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentPagerAdapter import ca.allanwang.kau.searchview.SearchItem @@ -165,9 +166,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, onNestedCreate(savedInstanceState) L.i { "Main finished loading UI in ${System.currentTimeMillis() - start} ms" } launch { - val tabs = tabDao.selectAll() - adapter.setPages(tabs) - viewPager.offscreenPageLimit = tabs.size + adapter.setPages(tabDao.selectAll()) } controlWebview = WebView(this) if (BuildConfig.VERSION_CODE > Prefs.versionCode) { @@ -551,6 +550,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, } lastPosition = 0 viewPager.setCurrentItem(0, false) + viewPager.offscreenPageLimit = pages.size viewPager.post { if (!fragmentChannel.isClosedForSend) fragmentChannel.offer(0) -- cgit v1.2.3 From 47ac9c218e92ec25b412e9c8c66812e53341052d Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 19:03:09 -0500 Subject: Specify coroutine scopes in base main activity --- .../main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 458e97c9..90fc8ea7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt @@ -32,7 +32,6 @@ import android.webkit.WebView import android.widget.FrameLayout import androidx.annotation.StringRes import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.core.view.size import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentPagerAdapter import ca.allanwang.kau.searchview.SearchItem @@ -286,7 +285,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, else when (profile.identifier) { -2L -> { // TODO no backpressure support - launch { + this@BaseMainActivity.launch { val currentCookie = cookieDao.currentCookie() if (currentCookie == null) { toast(R.string.account_not_found) @@ -315,7 +314,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, -3L -> launchNewTask(clearStack = false) -4L -> launchNewTask(cookies(), false) else -> { - launch { + this@BaseMainActivity.launch { FbCookie.switchUser(profile.identifier) tabsForEachView { _, view -> view.badgeText = null } refreshAll() -- cgit v1.2.3 From cac563f6c6a2656c74527bfa2c8b5780765baf69 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 19:25:11 -0500 Subject: Prepare generic db --- .../kotlin/com/pitchedapps/frost/db/GenericDb.kt | 72 ++++++++++++++++++++++ .../com/pitchedapps/frost/db/NotificationDb.kt | 3 + .../pitchedapps/frost/settings/Notifications.kt | 7 +-- 3 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt 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..c191b673 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt @@ -0,0 +1,72 @@ +/* + * 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 . + */ +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 +import com.pitchedapps.frost.utils.L + +/** + * 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") + suspend fun select(type: String): String? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun save(entity: GenericEntity) + + @Query("DELETE FROM frost_generic WHERE type = :type") + suspend fun delete(type: String) + + suspend fun saveTabs(tabs: List) { + val content = tabs.joinToString(",") { it.name } + save(GenericEntity(TYPE_TABS, content)) + } + + suspend fun getTabs(): List { + val allTabs = FbItem.values.map { it.name to it }.toMap() + return select(TYPE_TABS) + ?.split(",") + ?.mapNotNull { allTabs[it] } + ?.takeIf { it.isNotEmpty() } + ?: defaultTabs() + } + + companion object { + const val TYPE_TABS = "generic_tabs" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt index d2771754..01baa43e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt @@ -119,6 +119,9 @@ interface NotificationDao { @Query("DELETE FROM notifications WHERE userId = :userId AND type = :type") fun _deleteNotifications(userId: Long, type: String) + @Query("DELETE FROM notifications") + suspend fun deleteAll() + /** * It is assumed that the notification batch comes from the same user */ 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 3444d0b0..dafb259f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt @@ -30,7 +30,6 @@ import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R import com.pitchedapps.frost.activities.SettingsActivity import com.pitchedapps.frost.db.FrostDatabase -import com.pitchedapps.frost.db.NotificationModel import com.pitchedapps.frost.services.fetchNotifications import com.pitchedapps.frost.services.scheduleNotifications import com.pitchedapps.frost.utils.Prefs @@ -173,11 +172,7 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { plainText(R.string.reset_notif_epoch) { onClick = { launch { - FrostDatabase.get() - .cookieDao() - .selectAll() - .map { NotificationModel(it.id) } - .forEach { it.save() } + FrostDatabase.get().notifDao().deleteAll() } } } -- cgit v1.2.3 From 3f5d2cf2a55d28528c88e118f09a91fd6c59ac43 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 19:36:09 -0500 Subject: Replace tab dao with generic dao --- .../com/pitchedapps/frost/db/FbTabsDbTest.kt | 34 ------------ .../com/pitchedapps/frost/db/GenericDbTest.kt | 46 ++++++++++++++++ .../kotlin/com/pitchedapps/frost/StartActivity.kt | 12 ++--- .../frost/activities/BaseMainActivity.kt | 8 +-- .../frost/activities/TabCustomizerActivity.kt | 12 ++--- .../kotlin/com/pitchedapps/frost/db/Database.kt | 7 ++- .../kotlin/com/pitchedapps/frost/db/FbTabsDb.kt | 63 ---------------------- .../kotlin/com/pitchedapps/frost/db/GenericDb.kt | 29 +++++----- .../1.json | 20 +++---- 9 files changed, 89 insertions(+), 142 deletions(-) delete mode 100644 app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt create mode 100644 app/src/androidTest/kotlin/com/pitchedapps/frost/db/GenericDbTest.kt diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt deleted file mode 100644 index 752112f9..00000000 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/FbTabsDbTest.kt +++ /dev/null @@ -1,34 +0,0 @@ -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 FbTabsDbTest : BaseDbTest() { - - private val dao get() = db.tabDao() - - /** - * 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.save(tabs) - assertEquals(tabs, dao.selectAll(), "Tab saving failed") - val newTabs = listOf(FbItem.PAGES, FbItem.MENU) - dao.save(newTabs) - assertEquals(newTabs, dao.selectAll(), "Tab saving does not delete preexisting items") - } - } - - @Test - fun defaultRetrieve() { - runBlocking { - assertEquals(defaultTabs(), dao.selectAll(), "Default retrieval failed") - } - } -} \ No newline at end of file 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..9979eca4 --- /dev/null +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/GenericDbTest.kt @@ -0,0 +1,46 @@ +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" + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt index 24b848fe..87244864 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt @@ -35,10 +35,10 @@ import com.pitchedapps.frost.activities.SelectorActivity import com.pitchedapps.frost.db.CookieDao import com.pitchedapps.frost.db.CookieEntity import com.pitchedapps.frost.db.CookieModel -import com.pitchedapps.frost.db.FbTabDao import com.pitchedapps.frost.db.FbTabModel -import com.pitchedapps.frost.db.save -import com.pitchedapps.frost.db.selectAll +import com.pitchedapps.frost.db.GenericDao +import com.pitchedapps.frost.db.getTabs +import com.pitchedapps.frost.db.saveTabs import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.utils.EXTRA_COOKIES import com.pitchedapps.frost.utils.L @@ -59,7 +59,7 @@ import java.util.ArrayList class StartActivity : KauBaseActivity() { private val cookieDao: CookieDao by inject() - private val tabDao: FbTabDao by inject() + private val genericDao: GenericDao by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -114,8 +114,8 @@ class StartActivity : KauBaseActivity() { } val tabs = (select from FbTabModel::class).queryList().map(FbTabModel::tab) if (tabs.isNotEmpty()) { - tabDao.save(tabs) - L._d { "Migrated tabs ${tabDao.selectAll()}" } + genericDao.saveTabs(tabs) + L._d { "Migrated tabs ${genericDao.getTabs()}" } } deleteDatabase("Cookies.db") deleteDatabase("FrostTabs.db") 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 90fc8ea7..c43c31a2 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt @@ -70,9 +70,9 @@ import com.pitchedapps.frost.contracts.FileChooserDelegate import com.pitchedapps.frost.contracts.MainActivityContract import com.pitchedapps.frost.contracts.VideoViewHolder import com.pitchedapps.frost.db.CookieDao -import com.pitchedapps.frost.db.FbTabDao +import com.pitchedapps.frost.db.GenericDao import com.pitchedapps.frost.db.currentCookie -import com.pitchedapps.frost.db.selectAll +import com.pitchedapps.frost.db.getTabs import com.pitchedapps.frost.enums.MainActivityLayout import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.facebook.FbItem @@ -129,7 +129,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, override val frameWrapper: FrameLayout get() = frame_wrapper val viewPager: FrostViewPager get() = container val cookieDao: CookieDao by inject() - val tabDao: FbTabDao by inject() + val genericDao: GenericDao by inject() /* * Components with the same id in multiple layout files @@ -165,7 +165,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, onNestedCreate(savedInstanceState) L.i { "Main finished loading UI in ${System.currentTimeMillis() - start} ms" } launch { - adapter.setPages(tabDao.selectAll()) + adapter.setPages(genericDao.getTabs()) } controlWebview = WebView(this) if (BuildConfig.VERSION_CODE > Prefs.versionCode) { 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 7781e190..a380157f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt @@ -34,10 +34,10 @@ 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.db.FbTabDao +import com.pitchedapps.frost.db.GenericDao import com.pitchedapps.frost.db.TAB_COUNT -import com.pitchedapps.frost.db.save -import com.pitchedapps.frost.db.selectAll +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 @@ -54,7 +54,7 @@ import java.util.Collections */ class TabCustomizerActivity : BaseActivity() { - private val tabDao: FbTabDao by inject() + private val genericDao: GenericDao by inject() private val adapter = FastItemAdapter() @@ -74,7 +74,7 @@ class TabCustomizerActivity : BaseActivity() { instructions.setTextColor(Prefs.textColor) launch { - val tabs = tabDao.selectAll().toMutableList() + val tabs = genericDao.getTabs().toMutableList() L.d { "Tabs $tabs" } val remaining = FbItem.values().filter { it.name[0] != '_' }.toMutableList() remaining.removeAll(tabs) @@ -94,7 +94,7 @@ class TabCustomizerActivity : BaseActivity() { fab_save.setOnClickListener { launchMain(NonCancellable) { val tabs = adapter.adapterItems.subList(0, TAB_COUNT).map(TabIItem::item) - tabDao.save(tabs) + genericDao.saveTabs(tabs) setResult(Activity.RESULT_OK) finish() } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt index 5fc986e3..0c009634 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt @@ -27,11 +27,10 @@ abstract class FrostPrivateDatabase : RoomDatabase(), FrostPrivateDao { } interface FrostPublicDao { - fun tabDao(): FbTabDao + fun genericDao(): GenericDao } -@Database(entities = [FbTabEntity::class], version = 1, exportSchema = true) -@TypeConverters(FbItemConverter::class) +@Database(entities = [GenericEntity::class], version = 1, exportSchema = true) abstract class FrostPublicDatabase : RoomDatabase(), FrostPublicDao { companion object { const val DATABASE_NAME = "frost-db" @@ -71,9 +70,9 @@ class FrostDatabase(private val privateDb: FrostPrivateDatabase, private val pub fun module(context: Context) = module { single { create(context) } single { get().cookieDao() } - single { get().tabDao() } single { get().cacheDao() } single { get().notifDao() } + single { get().genericDao() } } /** diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt index f4e74509..c63be794 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt @@ -16,79 +16,16 @@ */ package com.pitchedapps.frost.db -import androidx.room.Dao -import androidx.room.Entity -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Transaction import com.pitchedapps.frost.facebook.FbItem -import com.pitchedapps.frost.facebook.defaultTabs 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.coroutines.Dispatchers -import kotlinx.coroutines.withContext /** * Created by Allan Wang on 2017-05-30. */ -@Entity(tableName = "tabs") -data class FbTabEntity(@androidx.room.PrimaryKey val position: Int, val tab: FbItem) - -@Dao -interface FbTabDao { - - @Query("SELECT * FROM tabs ORDER BY position ASC") - fun _selectAll(): List - - @Query("DELETE FROM tabs") - fun _deleteAll() - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun _insertAll(items: List) - - @Transaction - fun _save(items: List) { - _deleteAll() - _insertAll(items) - } -} - -/** - * Saving tabs operates by deleting all db items and saving the new list. - * Transactions can't be done with suspensions in room as switching threads during the process - * may result in a deadlock. - * That's why we disallow thread switching within the transaction, but wrap the entire thing in a coroutine - */ -suspend fun FbTabDao.save(items: List) { - withContext(Dispatchers.IO) { - val entities = (items.takeIf { it.isNotEmpty() } ?: defaultTabs()).mapIndexed { index, fbItem -> - FbTabEntity( - index, - fbItem - ) - } - _save(entities) - } -} - -suspend fun FbTabDao.selectAll(): List = withContext(Dispatchers.IO) { - _selectAll().map { it.tab }.takeIf { it.isNotEmpty() } ?: defaultTabs() -} - -object FbItemConverter { - @androidx.room.TypeConverter - @JvmStatic - fun fromItem(item: FbItem): String = item.name - - @androidx.room.TypeConverter - @JvmStatic - fun toItem(value: String): FbItem = FbItem.valueOf(value) -} - const val TAB_COUNT = 4 @Database(version = FbTabsDb.VERSION) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt index c191b673..c1f05092 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt @@ -24,7 +24,6 @@ import androidx.room.PrimaryKey import androidx.room.Query import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.defaultTabs -import com.pitchedapps.frost.utils.L /** * Created by Allan Wang on 2017-05-30. @@ -52,21 +51,21 @@ interface GenericDao { @Query("DELETE FROM frost_generic WHERE type = :type") suspend fun delete(type: String) - suspend fun saveTabs(tabs: List) { - val content = tabs.joinToString(",") { it.name } - save(GenericEntity(TYPE_TABS, content)) - } - - suspend fun getTabs(): List { - val allTabs = FbItem.values.map { it.name to it }.toMap() - return select(TYPE_TABS) - ?.split(",") - ?.mapNotNull { allTabs[it] } - ?.takeIf { it.isNotEmpty() } - ?: defaultTabs() - } - companion object { const val TYPE_TABS = "generic_tabs" } +} + +suspend fun GenericDao.saveTabs(tabs: List) { + val content = tabs.joinToString(",") { it.name } + save(GenericEntity(GenericDao.TYPE_TABS, content)) +} + +suspend fun GenericDao.getTabs(): List { + val allTabs = FbItem.values.map { it.name to it }.toMap() + return select(GenericDao.TYPE_TABS) + ?.split(",") + ?.mapNotNull { allTabs[it] } + ?.takeIf { it.isNotEmpty() } + ?: defaultTabs() } \ 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 index fe2aa83e..4a523c62 100644 --- a/app/src/schemas/com.pitchedapps.frost.db.FrostPublicDatabase/1.json +++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPublicDatabase/1.json @@ -2,28 +2,28 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "fde868470836ff9230f1d406922d7563", + "identityHash": "ee4d2fe4052ad3a1892be17681816c2c", "entities": [ { - "tableName": "tabs", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`position` INTEGER NOT NULL, `tab` TEXT NOT NULL, PRIMARY KEY(`position`))", + "tableName": "frost_generic", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `contents` TEXT NOT NULL, PRIMARY KEY(`type`))", "fields": [ { - "fieldPath": "position", - "columnName": "position", - "affinity": "INTEGER", + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", "notNull": true }, { - "fieldPath": "tab", - "columnName": "tab", + "fieldPath": "contents", + "columnName": "contents", "affinity": "TEXT", "notNull": true } ], "primaryKey": { "columnNames": [ - "position" + "type" ], "autoGenerate": false }, @@ -34,7 +34,7 @@ "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, \"fde868470836ff9230f1d406922d7563\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"ee4d2fe4052ad3a1892be17681816c2c\")" ] } } \ No newline at end of file -- cgit v1.2.3 From b1c7358c24ab95825d18293636251f276637ba25 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 7 Mar 2019 19:43:26 -0500 Subject: Apply spotless --- .../kotlin/com/pitchedapps/frost/db/BaseDbTest.kt | 18 +++++++++++++++++- .../kotlin/com/pitchedapps/frost/db/CacheDbTest.kt | 18 +++++++++++++++++- .../kotlin/com/pitchedapps/frost/db/CookieDbTest.kt | 18 +++++++++++++++++- .../kotlin/com/pitchedapps/frost/db/DatabaseTest.kt | 18 +++++++++++++++++- .../kotlin/com/pitchedapps/frost/db/GenericDbTest.kt | 18 +++++++++++++++++- .../com/pitchedapps/frost/db/NotificationDbTest.kt | 18 +++++++++++++++++- .../frost/activities/TabCustomizerActivity.kt | 1 - .../main/kotlin/com/pitchedapps/frost/db/CacheDb.kt | 2 +- .../main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt | 2 +- .../main/kotlin/com/pitchedapps/frost/db/Database.kt | 18 ++++++++++++++++-- .../main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt | 2 +- .../main/kotlin/com/pitchedapps/frost/db/GenericDb.kt | 2 +- .../kotlin/com/pitchedapps/frost/db/NotificationDb.kt | 2 +- app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt | 1 - .../com/pitchedapps/frost/views/FrostVideoViewer.kt | 1 - 15 files changed, 123 insertions(+), 16 deletions(-) diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/BaseDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/BaseDbTest.kt index 6621ea95..bae56e2f 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/BaseDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/BaseDbTest.kt @@ -1,3 +1,19 @@ +/* + * 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 . + */ package com.pitchedapps.frost.db import android.content.Context @@ -29,4 +45,4 @@ abstract class BaseDbTest { fun after() { db.close() } -} \ No newline at end of file +} diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt index 1fe7bbc4..417c6678 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CacheDbTest.kt @@ -1,3 +1,19 @@ +/* + * 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 . + */ package com.pitchedapps.frost.db import kotlinx.coroutines.runBlocking @@ -29,4 +45,4 @@ class CacheDbTest : BaseDbTest() { ) } } -} \ No newline at end of file +} diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt index 122e3205..327ead86 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/CookieDbTest.kt @@ -1,3 +1,19 @@ +/* + * 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 . + */ package com.pitchedapps.frost.db import kotlinx.coroutines.runBlocking @@ -66,4 +82,4 @@ class CookieDbTest : BaseDbTest() { assertNull(dao.selectById(cookie.id + 1), "Inexistent cookie selection failed") } } -} \ No newline at end of file +} diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/DatabaseTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/DatabaseTest.kt index 1f1a201b..c79d212e 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/DatabaseTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/DatabaseTest.kt @@ -1,3 +1,19 @@ +/* + * 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 . + */ package com.pitchedapps.frost.db import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -35,4 +51,4 @@ class DatabaseTest : KoinTest { val missingKoins = (members + FrostDatabase::class).filter { !hasKoin(it) } assertTrue(missingKoins.isEmpty(), "Missing koins: $missingKoins") } -} \ No newline at end of file +} diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/GenericDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/GenericDbTest.kt index 9979eca4..add9f509 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/GenericDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/GenericDbTest.kt @@ -1,3 +1,19 @@ +/* + * 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 . + */ package com.pitchedapps.frost.db import com.pitchedapps.frost.facebook.FbItem @@ -43,4 +59,4 @@ class GenericDbTest : BaseDbTest() { ) } } -} \ No newline at end of file +} diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt index 25c29db4..9167c7bf 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt @@ -1,3 +1,19 @@ +/* + * 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 . + */ package com.pitchedapps.frost.db import com.pitchedapps.frost.services.NOTIF_CHANNEL_GENERAL @@ -125,4 +141,4 @@ class NotificationDbTest : BaseDbTest() { ) } } -} \ No newline at end of file +} 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 a380157f..6ad7d3f2 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/TabCustomizerActivity.kt @@ -84,7 +84,6 @@ class TabCustomizerActivity : BaseActivity() { bindSwapper(adapter, tab_recycler) adapter.withOnClickListener { view, _, _, _ -> view!!.wobble(); true } - } setResult(Activity.RESULT_CANCELED) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt index 8c3c9c6b..4906f60a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt @@ -74,4 +74,4 @@ suspend fun CacheDao.save(id: Long, type: String, contents: String): Boolean = } catch (e: Exception) { L.e(e) { "Cache save failed for $type" } false - } \ No newline at end of file + } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt index 7e929370..5aadbb02 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt @@ -82,4 +82,4 @@ data class CookieModel(@PrimaryKey var id: Long = -1L, var name: String? = null, BaseModel(), Parcelable { override fun toString(): String = "CookieModel(${hashCode()})" -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt index 0c009634..e1b1d4c4 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/Database.kt @@ -1,11 +1,25 @@ +/* + * 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 . + */ package com.pitchedapps.frost.db import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase -import androidx.room.TypeConverters -import org.koin.core.Koin import org.koin.dsl.module.module import org.koin.standalone.StandAloneContext diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt index c63be794..a704ce82 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/FbTabsDb.kt @@ -35,4 +35,4 @@ object FbTabsDb { } @Table(database = FbTabsDb::class, allFields = true) -data class FbTabModel(@PrimaryKey var position: Int = -1, var tab: FbItem = FbItem.FEED) : BaseModel() \ No newline at end of file +data class FbTabModel(@PrimaryKey var position: Int = -1, var tab: FbItem = FbItem.FEED) : BaseModel() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt index c1f05092..4177ae86 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt @@ -68,4 +68,4 @@ suspend fun GenericDao.getTabs(): List { ?.mapNotNull { allTabs[it] } ?.takeIf { it.isNotEmpty() } ?: defaultTabs() -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt index 01baa43e..afeeefcd 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt @@ -194,4 +194,4 @@ data class NotificationModel( private fun lastNotificationTime(id: Long): NotificationModel = (select from NotificationModel::class where (NotificationModel_Table.id eq id)).querySingle() - ?: NotificationModel(id = id) \ No newline at end of file + ?: NotificationModel(id = id) 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 7d2e0e08..7c8c1895 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt @@ -20,7 +20,6 @@ import android.util.Log import ca.allanwang.kau.logging.KauLogger import com.bugsnag.android.Bugsnag import com.pitchedapps.frost.BuildConfig -import java.lang.Exception /** * Created by Allan Wang on 2017-05-28. 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 75b80ae1..4d88ad3d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt @@ -104,7 +104,6 @@ class FrostVideoViewer @JvmOverloads constructor( val cookie = FrostDatabase.get().cookieDao().currentCookie() ?: return@launchMain context.frostDownload(cookie, video.videoUri) } - } true } -- cgit v1.2.3 From e5eb928feacba8d1c634ad8e5d85259ae161cabd Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sat, 30 Mar 2019 22:30:16 -0400 Subject: Add thread progress --- .../com/pitchedapps/frost/db/NotificationDb.kt | 2 +- .../kotlin/com/pitchedapps/frost/db/ThreadDb.kt | 115 +++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/db/ThreadDb.kt diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt index afeeefcd..e1f7fc76 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt @@ -192,6 +192,6 @@ data class NotificationModel( var epochIm: Long = -1L ) : BaseModel() -private fun lastNotificationTime(id: Long): NotificationModel = +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/db/ThreadDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/ThreadDb.kt new file mode 100644 index 00000000..d7c91211 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/ThreadDb.kt @@ -0,0 +1,115 @@ +/* + * 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 . + */ +package com.pitchedapps.frost.db + +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.db.CookieModel_Table.cookie +import com.pitchedapps.frost.facebook.parsers.FrostThread +import com.pitchedapps.frost.utils.L +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +@Entity( + tableName = "threads", + primaryKeys = ["thread_id", "userId"], + foreignKeys = [ForeignKey( + entity = CookieEntity::class, + parentColumns = ["cookie_id"], + childColumns = ["userId"], + onDelete = ForeignKey.CASCADE + )], + indices = [Index("thread_id"), Index("userId")] +) +data class ThreadEntity( + @Embedded(prefix = "thread_") + val thread: FrostThread, + val userId: Long +) + +data class ThreadContentEntity( + @Embedded + val cookie: CookieEntity, + @Embedded(prefix = "thread_") + val thread: FrostThread +) + +@Dao +interface ThreadDao { + + /** + * Note that notifications are guaranteed to be ordered by descending timestamp + */ + @Transaction + @Query("SELECT * FROM cookies INNER JOIN threads ON cookie_id = userId WHERE userId = :userId ORDER BY thread_time DESC") + fun _selectThreads(userId: Long): List + + @Query("SELECT thread_time FROM threads WHERE userId = :userId ORDER BY thread_time DESC LIMIT 1") + fun _selectEpoch(userId: Long): Long? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun _insertThreads(notifs: List) + + @Query("DELETE FROM threads WHERE userId = :userId ") + fun _deleteThreads(userId: Long) + + @Query("DELETE FROM threads") + suspend fun deleteAll() + + /** + * It is assumed that the notification batch comes from the same user + */ + @Transaction + fun _saveThreads(userId: Long, notifs: List) { + val entities = notifs.map { ThreadEntity(it, userId) } + _deleteThreads(userId) + _insertThreads(entities) + } +} + +suspend fun ThreadDao.selectThreads(userId: Long): List = + withContext(Dispatchers.IO) { + _selectThreads(userId) + } + +/** + * Returns true if successful, given that there are constraints to the insertion + */ +suspend fun ThreadDao.saveThreads(userId: Long, threads: List): Boolean { + if (threads.isEmpty()) return true + return withContext(Dispatchers.IO) { + try { + _saveThreads(userId, threads) + true + } catch (e: Exception) { + L.e(e) { "Thread save failed" } + false + } + } +} + +suspend fun ThreadDao.latestEpoch(userId: Long, type: String): Long = + withContext(Dispatchers.IO) { + _selectEpoch(userId) ?: lastNotificationTime(userId).epochIm + } -- cgit v1.2.3 From b464f91668b1985e59b5555450011783da771211 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sun, 31 Mar 2019 20:15:03 -0400 Subject: Add stricter parsing tests --- .../com/pitchedapps/frost/facebook/parsers/FrostParser.kt | 12 ++++++++---- .../com/pitchedapps/frost/facebook/parsers/MessageParser.kt | 4 ++++ .../com/pitchedapps/frost/facebook/parsers/NotifParser.kt | 4 ++++ .../com/pitchedapps/frost/facebook/parsers/SearchParser.kt | 5 ++++- .../com/pitchedapps/frost/fragments/RecyclerFragmentBase.kt | 3 ++- .../com/pitchedapps/frost/facebook/parsers/FbParseTest.kt | 6 ++++-- 6 files changed, 26 insertions(+), 8 deletions(-) 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..90c8848c 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 @@ -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 { +interface FrostParser { /** * Name associated to parser @@ -76,11 +76,15 @@ const val FALLBACK_TIME_MOD = 1000000 data class FrostLink(val text: String, val href: String) -data class ParseResponse(val cookie: String, val data: T) { +data class ParseResponse(val cookie: String, val data: T) { override fun toString() = "ParseResponse\ncookie: $cookie\ndata:\n$data" } -interface ParseNotification { +interface ParseData { + val isEmpty: Boolean +} + +interface ParseNotification : ParseData { fun getUnreadNotifications(data: CookieModel): List } @@ -95,7 +99,7 @@ internal fun List.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(private val redirectToText: Boolean) : FrostParser { +internal abstract class FrostParserBase(private val redirectToText: Boolean) : FrostParser { 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..529ac23a 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 @@ -46,6 +46,10 @@ data class FrostMessages( val seeMore: FrostLink?, val extraLinks: List ) : ParseNotification { + + override val isEmpty: Boolean + get() = threads.isEmpty() + override fun toString() = StringBuilder().apply { append("FrostMessages {\n") append(threads.toJsonString("threads", 1)) 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..422ec384 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 @@ -36,6 +36,10 @@ data class FrostNotifs( val notifs: List, val seeMore: FrostLink? ) : ParseNotification { + + override val isEmpty: Boolean + get() = notifs.isEmpty() + override fun toString() = StringBuilder().apply { append("FrostNotifs {\n") append(notifs.toJsonString("notifs", 1)) 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) { +data class FrostSearches(val results: List) : 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> : RecyclerFragment open fun getAdapter(): FastAdapter> = fastAdapter(this.adapter) } -abstract class FrostParserFragment> : RecyclerFragment() { +abstract class FrostParserFragment> : RecyclerFragment() { /** * The parser to make this all happen 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..4fe43ca8 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 @@ -22,6 +22,7 @@ import com.pitchedapps.frost.internal.assertDescending import com.pitchedapps.frost.internal.authDependent import org.junit.BeforeClass import org.junit.Test +import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.test.fail @@ -39,13 +40,14 @@ class FbParseTest { } } - private inline fun FrostParser.test(action: T.() -> Unit = {}) = + private inline fun FrostParser.test(action: T.() -> Unit = {}) = parse(COOKIE).test(url, action) - private inline fun ParseResponse?.test(url: String, action: T.() -> Unit = {}) { + private inline fun ParseResponse?.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() } -- cgit v1.2.3 From 4739e6f58df1116babac69896764e83db551f583 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sun, 21 Apr 2019 20:26:01 -0400 Subject: Merge properties --- docs/Changelog.md | 6 ------ gradle.properties | 8 -------- 2 files changed, 14 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index 3d72ec4f..7a6bbfab 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,10 +1,5 @@ # Changelog -<<<<<<< HEAD -## v2.2.3 -* Add ability to hide stories -* Remove fbclid from urls -======= ## v2.2.4 * Show top bar to allow sharing posts * Fix unmuting videos when autoplay is enabled @@ -18,7 +13,6 @@ * Remove round icon settings as they are the default in Facebook * Update theme * Update translations ->>>>>>> dev ## v2.2.2 * New marketplace shortcut diff --git a/gradle.properties b/gradle.properties index c080daa0..4c0ace29 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,11 +14,7 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro APP_ID=Frost APP_GROUP=com.pitchedapps -<<<<<<< HEAD -KAU=4.0.0-alpha02 -======= KAU=4.0.0 ->>>>>>> dev KOTLIN=1.3.21 # https://mvnrepository.com/artifact/com.android.tools.build/gradle?repo=google @@ -60,13 +56,9 @@ MATERIAL_DRAWER_KT=2.0.1 # https://github.com/square/okhttp/releases OKHTTP=3.14.0 # http://robolectric.org/getting-started/ -<<<<<<< HEAD -ROBOELECTRIC=4.1 # https://developer.android.com/jetpack/androidx/releases/room ROOM=2.1.0-alpha04 -======= ROBOELECTRIC=4.2 ->>>>>>> dev # https://github.com/davemorrissey/subsampling-scale-image-view#quick-start SCALE_IMAGE_VIEW=3.10.0 # https://github.com/umano/AndroidSlidingUpPanel#importing-the-library -- cgit v1.2.3 From 5002792f2c79a7479c531736a4a9c40c0b1fe116 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sun, 21 Apr 2019 21:02:55 -0400 Subject: Wrap all db calls using our own context --- .../kotlin/com/pitchedapps/frost/StartActivity.kt | 2 + .../pitchedapps/frost/activities/LoginActivity.kt | 2 + .../kotlin/com/pitchedapps/frost/db/CacheDb.kt | 19 +++- .../kotlin/com/pitchedapps/frost/db/CookiesDb.kt | 15 ++- .../kotlin/com/pitchedapps/frost/db/DaoUtils.kt | 28 +++++ .../kotlin/com/pitchedapps/frost/db/GenericDb.kt | 14 +-- .../com/pitchedapps/frost/db/NotificationDb.kt | 17 ++- .../kotlin/com/pitchedapps/frost/db/ThreadDb.kt | 115 --------------------- .../com/pitchedapps/frost/facebook/FbCookie.kt | 3 + .../frost/services/NotificationService.kt | 1 + .../pitchedapps/frost/settings/Notifications.kt | 1 + .../com/pitchedapps/frost/web/DebugWebView.kt | 2 + 12 files changed, 78 insertions(+), 141 deletions(-) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/db/DaoUtils.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/db/ThreadDb.kt diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt index 87244864..18ae4b0b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt @@ -38,7 +38,9 @@ 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.EXTRA_COOKIES import com.pitchedapps.frost.utils.L 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 b80f06f7..ed207896 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt @@ -34,6 +34,8 @@ import com.bumptech.glide.request.target.Target import com.pitchedapps.frost.R 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 diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt index 4906f60a..f0dacdc7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CacheDb.kt @@ -55,23 +55,32 @@ data class CacheEntity( interface CacheDao { @Query("SELECT * FROM frost_cache WHERE id = :id AND type = :type") - suspend fun select(id: Long, type: String): CacheEntity? + fun _select(id: Long, type: String): CacheEntity? @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertCache(cache: CacheEntity) + fun _insertCache(cache: CacheEntity) @Query("DELETE FROM frost_cache WHERE id = :id AND type = :type") - suspend fun delete(id: Long, type: String) + 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 = +suspend fun CacheDao.save(id: Long, type: String, contents: String): Boolean = dao { try { - insertCache(CacheEntity(id, type, System.currentTimeMillis(), contents)) + _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 index 5aadbb02..b81ce365 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/CookiesDb.kt @@ -53,21 +53,26 @@ data class CookieEntity( interface CookieDao { @Query("SELECT * FROM cookies") - suspend fun selectAll(): List + fun _selectAll(): List @Query("SELECT * FROM cookies WHERE cookie_id = :id") - suspend fun selectById(id: Long): CookieEntity? + fun _selectById(id: Long): CookieEntity? @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun save(cookie: CookieEntity) + fun _save(cookie: CookieEntity) @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun save(cookies: List) + fun _save(cookies: List) @Query("DELETE FROM cookies WHERE cookie_id = :id") - suspend fun deleteById(id: Long) + 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) = dao { _save(cookies) } +suspend fun CookieDao.deleteById(id: Long) = dao { _deleteById(id) } suspend fun CookieDao.currentCookie() = selectById(Prefs.userId) @Database(version = CookiesDb.VERSION) 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 . + */ +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 dao(crossinline block: () -> T) = withContext(Dispatchers.IO) { block() } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt index 4177ae86..f36c8af9 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/GenericDb.kt @@ -43,27 +43,27 @@ data class GenericEntity( interface GenericDao { @Query("SELECT contents FROM frost_generic WHERE type = :type") - suspend fun select(type: String): String? + fun _select(type: String): String? @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun save(entity: GenericEntity) + fun _save(entity: GenericEntity) @Query("DELETE FROM frost_generic WHERE type = :type") - suspend fun delete(type: String) + fun _delete(type: String) companion object { const val TYPE_TABS = "generic_tabs" } } -suspend fun GenericDao.saveTabs(tabs: List) { +suspend fun GenericDao.saveTabs(tabs: List) = dao { val content = tabs.joinToString(",") { it.name } - save(GenericEntity(GenericDao.TYPE_TABS, content)) + _save(GenericEntity(GenericDao.TYPE_TABS, content)) } -suspend fun GenericDao.getTabs(): List { +suspend fun GenericDao.getTabs(): List = dao { val allTabs = FbItem.values.map { it.name to it }.toMap() - return select(GenericDao.TYPE_TABS) + _select(GenericDao.TYPE_TABS) ?.split(",") ?.mapNotNull { allTabs[it] } ?.takeIf { it.isNotEmpty() } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt index e1f7fc76..8936d682 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt @@ -42,8 +42,6 @@ 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 -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext @Entity( tableName = "notifications", @@ -120,7 +118,7 @@ interface NotificationDao { fun _deleteNotifications(userId: Long, type: String) @Query("DELETE FROM notifications") - suspend fun deleteAll() + fun _deleteAll() /** * It is assumed that the notification batch comes from the same user @@ -134,17 +132,18 @@ interface NotificationDao { } } -suspend fun NotificationDao.selectNotifications(userId: Long, type: String): List = - withContext(Dispatchers.IO) { - _selectNotifications(userId, type).map { it.toNotifContent() } - } +suspend fun NotificationDao.deleteAll() = dao { _deleteAll() } + +suspend fun NotificationDao.selectNotifications(userId: Long, type: String): List = dao { + _selectNotifications(userId, type).map { it.toNotifContent() } +} /** * Returns true if successful, given that there are constraints to the insertion */ suspend fun NotificationDao.saveNotifications(type: String, notifs: List): Boolean { if (notifs.isEmpty()) return true - return withContext(Dispatchers.IO) { + return dao { try { _saveNotifications(type, notifs) true @@ -155,7 +154,7 @@ suspend fun NotificationDao.saveNotifications(type: String, notifs: List it.epoch diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/ThreadDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/ThreadDb.kt deleted file mode 100644 index d7c91211..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/ThreadDb.kt +++ /dev/null @@ -1,115 +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 . - */ -package com.pitchedapps.frost.db - -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.db.CookieModel_Table.cookie -import com.pitchedapps.frost.facebook.parsers.FrostThread -import com.pitchedapps.frost.utils.L -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -@Entity( - tableName = "threads", - primaryKeys = ["thread_id", "userId"], - foreignKeys = [ForeignKey( - entity = CookieEntity::class, - parentColumns = ["cookie_id"], - childColumns = ["userId"], - onDelete = ForeignKey.CASCADE - )], - indices = [Index("thread_id"), Index("userId")] -) -data class ThreadEntity( - @Embedded(prefix = "thread_") - val thread: FrostThread, - val userId: Long -) - -data class ThreadContentEntity( - @Embedded - val cookie: CookieEntity, - @Embedded(prefix = "thread_") - val thread: FrostThread -) - -@Dao -interface ThreadDao { - - /** - * Note that notifications are guaranteed to be ordered by descending timestamp - */ - @Transaction - @Query("SELECT * FROM cookies INNER JOIN threads ON cookie_id = userId WHERE userId = :userId ORDER BY thread_time DESC") - fun _selectThreads(userId: Long): List - - @Query("SELECT thread_time FROM threads WHERE userId = :userId ORDER BY thread_time DESC LIMIT 1") - fun _selectEpoch(userId: Long): Long? - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun _insertThreads(notifs: List) - - @Query("DELETE FROM threads WHERE userId = :userId ") - fun _deleteThreads(userId: Long) - - @Query("DELETE FROM threads") - suspend fun deleteAll() - - /** - * It is assumed that the notification batch comes from the same user - */ - @Transaction - fun _saveThreads(userId: Long, notifs: List) { - val entities = notifs.map { ThreadEntity(it, userId) } - _deleteThreads(userId) - _insertThreads(entities) - } -} - -suspend fun ThreadDao.selectThreads(userId: Long): List = - withContext(Dispatchers.IO) { - _selectThreads(userId) - } - -/** - * Returns true if successful, given that there are constraints to the insertion - */ -suspend fun ThreadDao.saveThreads(userId: Long, threads: List): Boolean { - if (threads.isEmpty()) return true - return withContext(Dispatchers.IO) { - try { - _saveThreads(userId, threads) - true - } catch (e: Exception) { - L.e(e) { "Thread save failed" } - false - } - } -} - -suspend fun ThreadDao.latestEpoch(userId: Long, type: String): Long = - withContext(Dispatchers.IO) { - _selectEpoch(userId) ?: lastNotificationTime(userId).epochIm - } 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 6afbea4b..0c1da3a3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt @@ -22,6 +22,9 @@ import android.webkit.CookieManager 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 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 2e994577..760c681a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -23,6 +23,7 @@ import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R 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 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 dafb259f..c58710b5 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt @@ -30,6 +30,7 @@ import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R import com.pitchedapps.frost.activities.SettingsActivity 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 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}" } -- cgit v1.2.3 From 51e71caf4063702d88ad07b71a2cf9257175f71e Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sun, 21 Apr 2019 21:07:52 -0400 Subject: Move project to travis.com --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 30191f8f..5d2330d2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Frost-for-Facebook [![Releaes Version](https://img.shields.io/github/release/AllanWang/Frost-for-Facebook.svg)](https://github.com/AllanWang/Frost-for-Facebook/releases) -[![Build Status](https://travis-ci.org/AllanWang/Frost-for-Facebook.svg?branch=dev)](https://travis-ci.org/AllanWang/Frost-for-Facebook) +[![Build Status](https://travis-ci.com/AllanWang/Frost-for-Facebook.svg?branch=dev)](https://travis-ci.com/AllanWang/Frost-for-Facebook) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/frost-for-facebook/localized.svg)](https://crowdin.com/project/frost-for-facebook) [![ZenHub](https://img.shields.io/badge/Shipping%20faster%20with-ZenHub-45529A.svg)](https://app.zenhub.com/workspace/o/allanwang/frost-for-facebook/boards) [![BugSnag](https://img.shields.io/badge/Bug%20tracking%20with-BugSnag-37C2D9.svg)](https://www.bugsnag.com/) -- cgit v1.2.3 From 2fc18d0803c86a3e6892842136e9342dca31a53f Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sun, 21 Apr 2019 21:32:29 -0400 Subject: Add initial class --- .../kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt 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..308772d0 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt @@ -0,0 +1,7 @@ +package com.pitchedapps.frost.widgets + +import android.appwidget.AppWidgetProvider + +class NotificationWidget : AppWidgetProvider() { + +} \ No newline at end of file -- cgit v1.2.3 From 7f371a95320a1d8aae29f8ca15f7fd972367b60e Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 22 Apr 2019 03:21:30 -0400 Subject: Update parsers and add widget items --- app/src/main/AndroidManifest.xml | 14 +++ .../kotlin/com/pitchedapps/frost/db/Database.kt | 12 +- .../com/pitchedapps/frost/db/NotificationDb.kt | 9 +- .../frost/facebook/parsers/MessageParser.kt | 3 +- .../frost/facebook/parsers/NotifParser.kt | 3 +- .../frost/services/FrostNotifications.kt | 6 +- .../frost/widgets/NotificationWidget.kt | 135 ++++++++++++++++++++- .../main/res/layout/widget_notification_item.xml | 53 ++++++++ app/src/main/res/layout/widget_notifications.xml | 22 ++++ app/src/main/res/values/dimens.xml | 2 + app/src/main/res/xml/notification_widget_info.xml | 4 + .../1.json | 12 +- 12 files changed, 262 insertions(+), 13 deletions(-) create mode 100644 app/src/main/res/layout/widget_notification_item.xml create mode 100644 app/src/main/res/layout/widget_notifications.xml create mode 100644 app/src/main/res/xml/notification_widget_info.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ceb309f3..1ca91d62 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -173,6 +173,20 @@ + + + + + + + + + RoomDatabase.Builder.frostBuild() = if (BuildConfig.DEBUG) { + fallbackToDestructiveMigration().build() + } else { + build() + } + fun create(context: Context): FrostDatabase { val privateDb = Room.databaseBuilder( context, FrostPrivateDatabase::class.java, FrostPrivateDatabase.DATABASE_NAME - ).build() + ).frostBuild() val publicDb = Room.databaseBuilder( context, FrostPublicDatabase::class.java, FrostPublicDatabase.DATABASE_NAME - ).build() + ).frostBuild() return FrostDatabase(privateDb, publicDb) } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt index 8936d682..532bb435 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt @@ -64,7 +64,8 @@ data class NotificationEntity( val timestamp: Long, val profileUrl: String?, // Type essentially refers to channel - val type: String + val type: String, + val unread: Boolean ) { constructor( type: String, @@ -77,7 +78,8 @@ data class NotificationEntity( content.text, content.timestamp, content.profileUrl, - type + type, + content.unread ) } @@ -94,7 +96,8 @@ data class NotificationContentEntity( title = notif.title, text = notif.text, timestamp = notif.timestamp, - profileUrl = notif.profileUrl + profileUrl = notif.profileUrl, + unread = notif.unread ) } 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 80ed8ee8..9979cd2b 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 @@ -64,7 +64,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 199fc685..c22524ad 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 @@ -53,7 +53,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/services/FrostNotifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt index 6f039784..68ed859c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt @@ -181,7 +181,8 @@ 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) } @@ -266,7 +267,8 @@ data class NotificationContent( 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/widgets/NotificationWidget.kt b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt index 308772d0..e7a17c8e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt @@ -1,7 +1,140 @@ +/* + * 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 . + */ package com.pitchedapps.frost.widgets +import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider +import android.content.Context +import android.content.Intent +import android.widget.RemoteViews +import android.widget.RemoteViewsService +import androidx.annotation.ColorRes +import ca.allanwang.kau.utils.ContextHelper +import ca.allanwang.kau.utils.withAlpha +import com.bumptech.glide.request.target.AppWidgetTarget +import com.pitchedapps.frost.R +import com.pitchedapps.frost.db.NotificationDao +import com.pitchedapps.frost.db.selectNotifications +import com.pitchedapps.frost.glide.FrostGlide +import com.pitchedapps.frost.glide.GlideApp +import com.pitchedapps.frost.services.NOTIF_CHANNEL_GENERAL +import com.pitchedapps.frost.services.NotificationContent +import com.pitchedapps.frost.utils.Prefs +import com.pitchedapps.frost.widgets.NotificationWidget.Companion.NOTIF_WIDGET_IDS +import com.pitchedapps.frost.widgets.NotificationWidget.Companion.NOTIF_WIDGET_TYPE +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.runBlocking +import org.koin.standalone.KoinComponent +import org.koin.standalone.inject +import kotlin.coroutines.CoroutineContext class NotificationWidget : AppWidgetProvider() { + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + super.onUpdate(context, appWidgetManager, appWidgetIds) + val views = RemoteViews(context.packageName, com.pitchedapps.frost.R.layout.widget_notifications) + val intent = NotificationWidgetService.createIntent(context, NOTIF_CHANNEL_GENERAL, appWidgetIds) + for (id in appWidgetIds) { + views.setRemoteAdapter(R.id.widget_notification_list, intent) + appWidgetManager.updateAppWidget(id, views) + } + } -} \ No newline at end of file + companion object { + const val NOTIF_WIDGET_TYPE = "notif_widget_type" + const val NOTIF_WIDGET_IDS = "notif_widget_ids" + } +} + +class NotificationWidgetService : RemoteViewsService() { + override fun onGetViewFactory(intent: Intent): RemoteViewsFactory = NotificationWidgetDataProvider(this, intent) + + companion object { + fun createIntent(context: Context, type: String, appWidgetIds: IntArray): Intent = + Intent(context, NotificationWidgetService::class.java) + .putExtra(NOTIF_WIDGET_TYPE, type) + .putExtra(NOTIF_WIDGET_IDS, appWidgetIds) + } +} + +class NotificationWidgetDataProvider(val context: Context, val intent: Intent) : RemoteViewsService.RemoteViewsFactory, + CoroutineScope, KoinComponent { + + private val notifDao: NotificationDao by inject() + @Volatile + private var content: List = emptyList() + + private val type = intent.getStringExtra(NOTIF_WIDGET_TYPE) + + private val widgetIds = intent.getIntArrayExtra(NOTIF_WIDGET_IDS) + + private lateinit var job: Job + override val coroutineContext: CoroutineContext + get() = ContextHelper.dispatcher + job + + private suspend fun loadNotifications() { + content = notifDao.selectNotifications(Prefs.userId, type) + } + + override fun onCreate() { + job = SupervisorJob() + runBlocking { + loadNotifications() + } + } + + override fun onDataSetChanged() { + runBlocking { + 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 glide = GlideApp.with(context).asBitmap() + 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.toString()) // TODO + glide.load(notif.profileUrl).transform(FrostGlide.circleCrop) + .into(AppWidgetTarget(context, R.id.item_avatar, views)) + return views + } + + private fun RemoteViews.setBackgroundColor(viewId: Int, @ColorRes color: Int) { + setInt(viewId, "setBackgroundColor", color) + } + + override fun getCount(): Int = content.size + + override fun getViewTypeCount(): Int { + TODO("not implemented") + } + + override fun onDestroy() { + job.cancel() + } +} 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..d02e2611 --- /dev/null +++ b/app/src/main/res/layout/widget_notification_item.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + \ 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..fd68e20a --- /dev/null +++ b/app/src/main/res/layout/widget_notifications.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 713bd1b4..53dad5ef 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -9,4 +9,6 @@ 50dp 64dp 20dp + + 18dp 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..3cb97ed7 --- /dev/null +++ b/app/src/main/res/xml/notification_widget_info.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json index 60d5cddd..a0cc2c2a 100644 --- a/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json +++ b/app/src/schemas/com.pitchedapps.frost.db.FrostPrivateDatabase/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "0a9d994786b7e07fea95c11d9210ce0e", + "identityHash": "fe8f5b6c27f48d7e0733ee6819f06f40", "entities": [ { "tableName": "cookies", @@ -38,7 +38,7 @@ }, { "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, PRIMARY KEY(`notif_id`, `userId`), FOREIGN KEY(`userId`) REFERENCES `cookies`(`cookie_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "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", @@ -87,6 +87,12 @@ "columnName": "type", "affinity": "TEXT", "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true } ], "primaryKey": { @@ -183,7 +189,7 @@ "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, \"0a9d994786b7e07fea95c11d9210ce0e\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"fe8f5b6c27f48d7e0733ee6819f06f40\")" ] } } \ No newline at end of file -- cgit v1.2.3 From ba0b4e70d93f91497a7bc157fe7f3838942dfb15 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 22 Apr 2019 04:42:52 -0400 Subject: Fix icon loading --- .../com/pitchedapps/frost/db/NotificationDb.kt | 5 +- .../frost/widgets/NotificationWidget.kt | 60 +++++++++------------- app/src/main/res/drawable/ic_refresh_24dp.xml | 5 ++ app/src/main/res/layout/widget_notifications.xml | 20 +++++--- app/src/main/res/values/dimens.xml | 2 +- app/src/main/res/xml/notification_widget_info.xml | 8 +-- 6 files changed, 54 insertions(+), 46 deletions(-) create mode 100644 app/src/main/res/drawable/ic_refresh_24dp.xml diff --git a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt index 532bb435..d4b51c1e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/db/NotificationDb.kt @@ -137,8 +137,11 @@ interface NotificationDao { suspend fun NotificationDao.deleteAll() = dao { _deleteAll() } -suspend fun NotificationDao.selectNotifications(userId: Long, type: String): List = dao { +fun NotificationDao.selectNotificationsSync(userId: Long, type: String): List = _selectNotifications(userId, type).map { it.toNotifContent() } + +suspend fun NotificationDao.selectNotifications(userId: Long, type: String): List = dao { + selectNotificationsSync(userId, type) } /** diff --git a/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt index e7a17c8e..6ba0a3d6 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt @@ -22,37 +22,34 @@ import android.content.Context import android.content.Intent import android.widget.RemoteViews import android.widget.RemoteViewsService -import androidx.annotation.ColorRes -import ca.allanwang.kau.utils.ContextHelper +import androidx.annotation.ColorInt +import ca.allanwang.kau.utils.dimenPixelSize import ca.allanwang.kau.utils.withAlpha -import com.bumptech.glide.request.target.AppWidgetTarget import com.pitchedapps.frost.R import com.pitchedapps.frost.db.NotificationDao -import com.pitchedapps.frost.db.selectNotifications +import com.pitchedapps.frost.db.selectNotificationsSync import com.pitchedapps.frost.glide.FrostGlide import com.pitchedapps.frost.glide.GlideApp import com.pitchedapps.frost.services.NOTIF_CHANNEL_GENERAL import com.pitchedapps.frost.services.NotificationContent +import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.widgets.NotificationWidget.Companion.NOTIF_WIDGET_IDS import com.pitchedapps.frost.widgets.NotificationWidget.Companion.NOTIF_WIDGET_TYPE -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.runBlocking import org.koin.standalone.KoinComponent import org.koin.standalone.inject -import kotlin.coroutines.CoroutineContext class NotificationWidget : AppWidgetProvider() { override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { super.onUpdate(context, appWidgetManager, appWidgetIds) - val views = RemoteViews(context.packageName, com.pitchedapps.frost.R.layout.widget_notifications) val intent = NotificationWidgetService.createIntent(context, NOTIF_CHANNEL_GENERAL, appWidgetIds) for (id in appWidgetIds) { + val views = RemoteViews(context.packageName, R.layout.widget_notifications) + views.setBackgroundColor(R.id.widget_layout_container, Prefs.bgColor) views.setRemoteAdapter(R.id.widget_notification_list, intent) appWidgetManager.updateAppWidget(id, views) } + appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.widget_notification_list) } companion object { @@ -61,6 +58,10 @@ class NotificationWidget : AppWidgetProvider() { } } +private fun RemoteViews.setBackgroundColor(viewId: Int, @ColorInt color: Int) { + setInt(viewId, "setBackgroundColor", color) +} + class NotificationWidgetService : RemoteViewsService() { override fun onGetViewFactory(intent: Intent): RemoteViewsFactory = NotificationWidgetDataProvider(this, intent) @@ -73,7 +74,7 @@ class NotificationWidgetService : RemoteViewsService() { } class NotificationWidgetDataProvider(val context: Context, val intent: Intent) : RemoteViewsService.RemoteViewsFactory, - CoroutineScope, KoinComponent { + KoinComponent { private val notifDao: NotificationDao by inject() @Volatile @@ -83,25 +84,20 @@ class NotificationWidgetDataProvider(val context: Context, val intent: Intent) : private val widgetIds = intent.getIntArrayExtra(NOTIF_WIDGET_IDS) - private lateinit var job: Job - override val coroutineContext: CoroutineContext - get() = ContextHelper.dispatcher + job + private val avatarSize = context.dimenPixelSize(R.dimen.avatar_image_size) + + private val glide = GlideApp.with(context).asBitmap() - private suspend fun loadNotifications() { - content = notifDao.selectNotifications(Prefs.userId, type) + private fun loadNotifications() { + content = notifDao.selectNotificationsSync(Prefs.userId, type) + L._d { "Updated notif widget with ${content.size} items" } } override fun onCreate() { - job = SupervisorJob() - runBlocking { - loadNotifications() - } } override fun onDataSetChanged() { - runBlocking { - loadNotifications() - } + loadNotifications() } override fun getLoadingView(): RemoteViews? = null @@ -112,29 +108,23 @@ class NotificationWidgetDataProvider(val context: Context, val intent: Intent) : override fun getViewAt(position: Int): RemoteViews { val views = RemoteViews(context.packageName, R.layout.widget_notification_item) - val glide = GlideApp.with(context).asBitmap() val notif = content[position] + L._d { "View $position $notif" } 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.toString()) // TODO - glide.load(notif.profileUrl).transform(FrostGlide.circleCrop) - .into(AppWidgetTarget(context, R.id.item_avatar, views)) +// views.setOnClickPendingIntent() + val avatar = glide.load(notif.profileUrl).transform(FrostGlide.circleCrop).submit(avatarSize, avatarSize).get() + views.setImageViewBitmap(R.id.item_avatar, avatar) return views } - private fun RemoteViews.setBackgroundColor(viewId: Int, @ColorRes color: Int) { - setInt(viewId, "setBackgroundColor", color) - } - override fun getCount(): Int = content.size - override fun getViewTypeCount(): Int { - TODO("not implemented") - } + override fun getViewTypeCount(): Int = 1 override fun onDestroy() { - job.cancel() } -} +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_refresh_24dp.xml b/app/src/main/res/drawable/ic_refresh_24dp.xml new file mode 100644 index 00000000..cc2d1e04 --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/widget_notifications.xml b/app/src/main/res/layout/widget_notifications.xml index fd68e20a..098bb9eb 100644 --- a/app/src/main/res/layout/widget_notifications.xml +++ b/app/src/main/res/layout/widget_notifications.xml @@ -1,22 +1,30 @@ - + + + + android:layout_gravity="center_vertical" + android:src="@drawable/ic_refresh_24dp" /> + - + android:layout_height="0dp" + android:layout_weight="1" /> \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 53dad5ef..847e74cb 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -10,5 +10,5 @@ 64dp 20dp - 18dp + 24dp diff --git a/app/src/main/res/xml/notification_widget_info.xml b/app/src/main/res/xml/notification_widget_info.xml index 3cb97ed7..2b8b4fb5 100644 --- a/app/src/main/res/xml/notification_widget_info.xml +++ b/app/src/main/res/xml/notification_widget_info.xml @@ -1,4 +1,6 @@ - - - \ No newline at end of file + -- cgit v1.2.3 From 9df2a878226fb5f7adcf1af61a1ce87dff826fbc Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 22 Apr 2019 04:44:19 -0400 Subject: Remove unnecessary id --- .../com/pitchedapps/frost/widgets/NotificationWidget.kt | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt index 6ba0a3d6..4ddd7225 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt @@ -34,15 +34,13 @@ import com.pitchedapps.frost.services.NOTIF_CHANNEL_GENERAL import com.pitchedapps.frost.services.NotificationContent import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs -import com.pitchedapps.frost.widgets.NotificationWidget.Companion.NOTIF_WIDGET_IDS -import com.pitchedapps.frost.widgets.NotificationWidget.Companion.NOTIF_WIDGET_TYPE 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 intent = NotificationWidgetService.createIntent(context, NOTIF_CHANNEL_GENERAL, appWidgetIds) + val intent = NotificationWidgetService.createIntent(context, NOTIF_CHANNEL_GENERAL) for (id in appWidgetIds) { val views = RemoteViews(context.packageName, R.layout.widget_notifications) views.setBackgroundColor(R.id.widget_layout_container, Prefs.bgColor) @@ -51,13 +49,10 @@ class NotificationWidget : AppWidgetProvider() { } appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.widget_notification_list) } - - companion object { - const val NOTIF_WIDGET_TYPE = "notif_widget_type" - const val NOTIF_WIDGET_IDS = "notif_widget_ids" - } } +private const val NOTIF_WIDGET_TYPE = "notif_widget_type" + private fun RemoteViews.setBackgroundColor(viewId: Int, @ColorInt color: Int) { setInt(viewId, "setBackgroundColor", color) } @@ -66,10 +61,9 @@ class NotificationWidgetService : RemoteViewsService() { override fun onGetViewFactory(intent: Intent): RemoteViewsFactory = NotificationWidgetDataProvider(this, intent) companion object { - fun createIntent(context: Context, type: String, appWidgetIds: IntArray): Intent = + fun createIntent(context: Context, type: String): Intent = Intent(context, NotificationWidgetService::class.java) .putExtra(NOTIF_WIDGET_TYPE, type) - .putExtra(NOTIF_WIDGET_IDS, appWidgetIds) } } @@ -82,8 +76,6 @@ class NotificationWidgetDataProvider(val context: Context, val intent: Intent) : private val type = intent.getStringExtra(NOTIF_WIDGET_TYPE) - private val widgetIds = intent.getIntArrayExtra(NOTIF_WIDGET_IDS) - private val avatarSize = context.dimenPixelSize(R.dimen.avatar_image_size) private val glide = GlideApp.with(context).asBitmap() -- cgit v1.2.3 From 7e227078373adf0ef8c243b7e1e05804524cd3ea Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 24 Apr 2019 19:12:25 -0700 Subject: Add click events to notifications --- .../com/pitchedapps/frost/db/NotificationDbTest.kt | 3 +- .../frost/services/FrostNotifications.kt | 36 +++++++--- .../com/pitchedapps/frost/utils/TimeUtils.kt | 2 + .../kotlin/com/pitchedapps/frost/utils/Utils.kt | 2 +- .../frost/widgets/NotificationWidget.kt | 81 +++++++++++++++++++--- .../main/res/layout/widget_notification_item.xml | 15 ++-- app/src/main/res/layout/widget_notifications.xml | 9 +-- app/src/main/res/values/strings.xml | 10 +++ 8 files changed, 122 insertions(+), 36 deletions(-) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/utils/TimeUtils.kt diff --git a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt index 9167c7bf..45a09cbe 100644 --- a/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt +++ b/app/src/androidTest/kotlin/com/pitchedapps/frost/db/NotificationDbTest.kt @@ -38,7 +38,8 @@ class NotificationDbTest : BaseDbTest() { title = null, text = "", timestamp = time, - profileUrl = null + profileUrl = null, + unread = true ) @Test 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 68ed859c..1c37bc29 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt @@ -61,7 +61,7 @@ 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, @@ -95,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() @@ -187,18 +187,34 @@ enum class NotificationType( 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) 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..1ca6333b --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/TimeUtils.kt @@ -0,0 +1,2 @@ +package com.pitchedapps.frost.utils + 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 711d7e18..76ffd8cd 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt @@ -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/widgets/NotificationWidget.kt b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt index 4ddd7225..ae45fa7b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt @@ -16,35 +16,69 @@ */ package com.pitchedapps.frost.widgets +import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider 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.string 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.NOTIF_CHANNEL_GENERAL import com.pitchedapps.frost.services.NotificationContent +import com.pitchedapps.frost.services.NotificationType import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import org.koin.standalone.KoinComponent import org.koin.standalone.inject +import java.text.DateFormat class NotificationWidget : AppWidgetProvider() { override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { super.onUpdate(context, appWidgetManager, appWidgetIds) - val intent = NotificationWidgetService.createIntent(context, NOTIF_CHANNEL_GENERAL) + 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_container, Prefs.bgColor) + + 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) @@ -52,18 +86,44 @@ class NotificationWidget : AppWidgetProvider() { } private const val NOTIF_WIDGET_TYPE = "notif_widget_type" +private const val NOTIF_WIDGET_USER_ID = "notif_widget_user_id" +private const val NOTIF_WIDGET_USER_COOKIE = "notif_widget_user_id" -private fun RemoteViews.setBackgroundColor(viewId: Int, @ColorInt color: Int) { +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: String): Intent = + fun createIntent(context: Context, type: NotificationType, userId: Long): Intent = Intent(context, NotificationWidgetService::class.java) - .putExtra(NOTIF_WIDGET_TYPE, type) + .putExtra(NOTIF_WIDGET_TYPE, type.name) + .putExtra(NOTIF_WIDGET_USER_ID, userId) } } @@ -74,14 +134,16 @@ class NotificationWidgetDataProvider(val context: Context, val intent: Intent) : @Volatile private var content: List = emptyList() - private val type = intent.getStringExtra(NOTIF_WIDGET_TYPE) + 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(Prefs.userId, type) + content = notifDao.selectNotificationsSync(userId, type.channelId) L._d { "Updated notif widget with ${content.size} items" } } @@ -107,9 +169,10 @@ class NotificationWidgetDataProvider(val context: Context, val intent: Intent) : 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.toString()) // TODO -// views.setOnClickPendingIntent() + 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 } diff --git a/app/src/main/res/layout/widget_notification_item.xml b/app/src/main/res/layout/widget_notification_item.xml index d02e2611..f36f2766 100644 --- a/app/src/main/res/layout/widget_notification_item.xml +++ b/app/src/main/res/layout/widget_notification_item.xml @@ -1,22 +1,19 @@ + android:layout_height="@dimen/avatar_image_size" /> + %s at %s + -- cgit v1.2.3 From 27d1b98330c25e8833279ed48a4d9bd7d7566d10 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 24 Apr 2019 19:38:54 -0700 Subject: Create readable time converter --- .../com/pitchedapps/frost/utils/TimeUtils.kt | 48 ++++++++++++++++++++++ .../frost/widgets/NotificationWidget.kt | 7 ++-- app/src/main/res/values/strings.xml | 4 +- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/TimeUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/TimeUtils.kt index 1ca6333b..c0feab1e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/TimeUtils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/TimeUtils.kt @@ -1,2 +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 . + */ 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/widgets/NotificationWidget.kt b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt index ae45fa7b..1897b042 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt @@ -35,7 +35,6 @@ import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import androidx.annotation.IdRes import ca.allanwang.kau.utils.dimenPixelSize -import ca.allanwang.kau.utils.string import ca.allanwang.kau.utils.withAlpha import com.pitchedapps.frost.R import com.pitchedapps.frost.activities.MainActivity @@ -47,9 +46,9 @@ import com.pitchedapps.frost.services.NotificationContent import com.pitchedapps.frost.services.NotificationType import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs +import com.pitchedapps.frost.utils.toReadableTime import org.koin.standalone.KoinComponent import org.koin.standalone.inject -import java.text.DateFormat class NotificationWidget : AppWidgetProvider() { override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { @@ -168,7 +167,7 @@ class NotificationWidgetDataProvider(val context: Context, val intent: Intent) : 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.toString()) // TODO + 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) @@ -182,4 +181,4 @@ class NotificationWidgetDataProvider(val context: Context, val intent: Intent) : override fun onDestroy() { } -} \ No newline at end of file +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2b6a2ba6..0c2e625c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -69,7 +69,9 @@ 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 --> - %s at %s + %1s at %2s -- cgit v1.2.3 From 14578e26fcd39cf671aa4584d3abf311a66afba2 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 24 Apr 2019 19:51:37 -0700 Subject: Apply updates from services --- .../com/pitchedapps/frost/activities/BaseMainActivity.kt | 7 ++++++- .../com/pitchedapps/frost/services/NotificationService.kt | 4 ++++ .../com/pitchedapps/frost/widgets/NotificationWidget.kt | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) 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 c43c31a2..d6f1abe7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt @@ -104,6 +104,7 @@ 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.* @@ -450,7 +451,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 */ 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 760c681a..0eee5558 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -27,6 +27,7 @@ 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 @@ -105,6 +106,9 @@ 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) + } } /** diff --git a/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt index 1897b042..57cc4fe1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt @@ -19,6 +19,7 @@ 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 @@ -51,6 +52,7 @@ 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 @@ -82,6 +84,18 @@ class NotificationWidget : AppWidgetProvider() { } 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" -- cgit v1.2.3 From e627bc4f1fbf50216f74c53c6a4812beb6d2d055 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 24 Apr 2019 23:54:27 -0700 Subject: Remove unnecessary components --- .../frost/widgets/NotificationWidget.kt | 4 -- app/src/main/res/drawable/ic_refresh_24dp.xml | 5 --- .../res/drawable/notification_widget_preview.xml | 49 ++++++++++++++++++++++ app/src/main/res/xml/notification_widget_info.xml | 10 +++-- 4 files changed, 56 insertions(+), 12 deletions(-) delete mode 100644 app/src/main/res/drawable/ic_refresh_24dp.xml create mode 100644 app/src/main/res/drawable/notification_widget_preview.xml diff --git a/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt index 57cc4fe1..594da00a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/widgets/NotificationWidget.kt @@ -45,7 +45,6 @@ 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.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.toReadableTime import org.koin.standalone.KoinComponent @@ -100,7 +99,6 @@ class NotificationWidget : AppWidgetProvider() { private const val NOTIF_WIDGET_TYPE = "notif_widget_type" private const val NOTIF_WIDGET_USER_ID = "notif_widget_user_id" -private const val NOTIF_WIDGET_USER_COOKIE = "notif_widget_user_id" private fun RemoteViews.setBackgroundColor(@IdRes viewId: Int, @ColorInt color: Int) { setInt(viewId, "setBackgroundColor", color) @@ -157,7 +155,6 @@ class NotificationWidgetDataProvider(val context: Context, val intent: Intent) : private fun loadNotifications() { content = notifDao.selectNotificationsSync(userId, type.channelId) - L._d { "Updated notif widget with ${content.size} items" } } override fun onCreate() { @@ -176,7 +173,6 @@ class NotificationWidgetDataProvider(val context: Context, val intent: Intent) : override fun getViewAt(position: Int): RemoteViews { val views = RemoteViews(context.packageName, R.layout.widget_notification_item) val notif = content[position] - L._d { "View $position $notif" } 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) diff --git a/app/src/main/res/drawable/ic_refresh_24dp.xml b/app/src/main/res/drawable/ic_refresh_24dp.xml deleted file mode 100644 index cc2d1e04..00000000 --- a/app/src/main/res/drawable/ic_refresh_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - 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..a4ead7d0 --- /dev/null +++ b/app/src/main/res/drawable/notification_widget_preview.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/notification_widget_info.xml b/app/src/main/res/xml/notification_widget_info.xml index 2b8b4fb5..c14bbfb2 100644 --- a/app/src/main/res/xml/notification_widget_info.xml +++ b/app/src/main/res/xml/notification_widget_info.xml @@ -1,6 +1,10 @@ - + + android:minWidth="180dp" + android:minHeight="250dp" + android:previewImage="@drawable/notification_widget_preview" /> -- cgit v1.2.3 From 2fb01ffb24b0a80560bb7bbc89d2fa6f6bdb65fb Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 25 Apr 2019 13:05:02 -0700 Subject: Add widget preview --- .../res/drawable/notification_widget_preview.xml | 97 +++++++++++----------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/app/src/main/res/drawable/notification_widget_preview.xml b/app/src/main/res/drawable/notification_widget_preview.xml index a4ead7d0..a03fd362 100644 --- a/app/src/main/res/drawable/notification_widget_preview.xml +++ b/app/src/main/res/drawable/notification_widget_preview.xml @@ -1,49 +1,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + -- cgit v1.2.3 From cf152584b544425e0ee3ae28d765dc7e55a196af Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 25 Apr 2019 13:06:37 -0700 Subject: Update changelog --- app/src/main/res/xml/frost_changelog.xml | 12 +++++++++++- docs/Changelog.md | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) 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 @@ --> + + + + + + + + + + + - 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 -- cgit v1.2.3 From 00f25fee8c1b1f002a92d6c9b16088b1b8b33ba4 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 25 Apr 2019 15:24:01 -0700 Subject: Close drawer on back if opened, resolves #1407 --- .../main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt | 4 ++++ 1 file changed, 4 insertions(+) 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 d6f1abe7..d8b29ddc 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt @@ -508,6 +508,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 { -- cgit v1.2.3 From 77d28c77ead6a2477e1e4df1b8e74bd08a3c2efd Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 25 Apr 2019 15:39:04 -0700 Subject: Move search view binding to separate func --- .../kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt | 6 +++++- app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) 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 d8b29ddc..49d5f8bf 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt @@ -383,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 -> @@ -414,7 +419,6 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, onItemClick = { _, key, _, _ -> launchWebOverlay(key) } } } - return true } @SuppressLint("RestrictedApi") 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"), ; -- cgit v1.2.3 From b8ab09e645333a76aee19637bda3c4e76f51f5e1 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 25 Apr 2019 15:43:38 -0700 Subject: Update some tests --- .../kotlin/com/pitchedapps/frost/facebook/parsers/FbParseTest.kt | 2 ++ app/src/test/kotlin/com/pitchedapps/frost/internal/Internal.kt | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) 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 4fe43ca8..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,6 +21,7 @@ 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 @@ -64,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) } /** -- cgit v1.2.3 From a95ff37eaa30106d08144023e1d6f56e0cd3e119 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 25 Apr 2019 16:53:50 -0700 Subject: Clean urls before sharing --- .../kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 accf9d98..f3226e2f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt @@ -312,8 +312,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) } -- cgit v1.2.3 From d26d3fe99d8d1b65e9b54efa1ac77e877223fb81 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 25 Apr 2019 17:01:10 -0700 Subject: Update favicon --- Test root directory favicon Test head replacement Use icon instead of shortcut icon Remove stylesheet Try minima Replace style Revert to jekyll Update default Add all remaining icons Move favicon out --- _layouts/default.html | 78 +++++++++++++++++++++++++++++++++++++ favicon.ico | Bin 0 -> 15086 bytes favicon/android-chrome-192x192.png | Bin 0 -> 2216 bytes favicon/android-chrome-512x512.png | Bin 0 -> 6234 bytes favicon/apple-touch-icon.png | Bin 0 -> 2133 bytes favicon/browserconfig.xml | 9 +++++ favicon/favicon-16x16.png | Bin 0 -> 556 bytes favicon/favicon-32x32.png | Bin 0 -> 667 bytes favicon/mstile-144x144.png | Bin 0 -> 1472 bytes favicon/mstile-150x150.png | Bin 0 -> 1490 bytes favicon/mstile-310x150.png | Bin 0 -> 1665 bytes favicon/mstile-310x310.png | Bin 0 -> 3041 bytes favicon/mstile-70x70.png | Bin 0 -> 1105 bytes favicon/safari-pinned-tab.svg | 27 +++++++++++++ favicon/site.webmanifest | 19 +++++++++ 15 files changed, 133 insertions(+) create mode 100644 _layouts/default.html create mode 100644 favicon.ico create mode 100644 favicon/android-chrome-192x192.png create mode 100644 favicon/android-chrome-512x512.png create mode 100644 favicon/apple-touch-icon.png create mode 100644 favicon/browserconfig.xml create mode 100644 favicon/favicon-16x16.png create mode 100644 favicon/favicon-32x32.png create mode 100644 favicon/mstile-144x144.png create mode 100644 favicon/mstile-150x150.png create mode 100644 favicon/mstile-310x150.png create mode 100644 favicon/mstile-310x310.png create mode 100644 favicon/mstile-70x70.png create mode 100644 favicon/safari-pinned-tab.svg create mode 100644 favicon/site.webmanifest diff --git a/_layouts/default.html b/_layouts/default.html new file mode 100644 index 00000000..f8b9f991 --- /dev/null +++ b/_layouts/default.html @@ -0,0 +1,78 @@ + + + + + + + + + {% seo %} + + + + + + + + + + + + + + + + +
+
+

{{ site.title | default: site.github.repository_name }}

+ + {% if site.logo %} + Logo + {% endif %} + +

{{ site.description | default: site.github.project_tagline }}

+ + {% if site.github.is_project_page %} +

View the Project on GitHub {{ site.github.repository_nwo }}

+ {% endif %} + + {% if site.github.is_user_page %} +

View My GitHub Profile

+ {% endif %} + + {% if site.show_downloads %} + + {% endif %} +
+
+ + {{ content }} + +
+ +
+ + {% if site.google_analytics %} + +{% endif %} + + \ No newline at end of file diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 00000000..ec0bf22c Binary files /dev/null and b/favicon.ico differ diff --git a/favicon/android-chrome-192x192.png b/favicon/android-chrome-192x192.png new file mode 100644 index 00000000..49791799 Binary files /dev/null and b/favicon/android-chrome-192x192.png differ diff --git a/favicon/android-chrome-512x512.png b/favicon/android-chrome-512x512.png new file mode 100644 index 00000000..960b1818 Binary files /dev/null and b/favicon/android-chrome-512x512.png differ diff --git a/favicon/apple-touch-icon.png b/favicon/apple-touch-icon.png new file mode 100644 index 00000000..1fa3adf2 Binary files /dev/null and b/favicon/apple-touch-icon.png differ diff --git a/favicon/browserconfig.xml b/favicon/browserconfig.xml new file mode 100644 index 00000000..ed5dd798 --- /dev/null +++ b/favicon/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/favicon/favicon-16x16.png b/favicon/favicon-16x16.png new file mode 100644 index 00000000..e43b849d Binary files /dev/null and b/favicon/favicon-16x16.png differ diff --git a/favicon/favicon-32x32.png b/favicon/favicon-32x32.png new file mode 100644 index 00000000..bef47432 Binary files /dev/null and b/favicon/favicon-32x32.png differ diff --git a/favicon/mstile-144x144.png b/favicon/mstile-144x144.png new file mode 100644 index 00000000..5e4aa92d Binary files /dev/null and b/favicon/mstile-144x144.png differ diff --git a/favicon/mstile-150x150.png b/favicon/mstile-150x150.png new file mode 100644 index 00000000..da592590 Binary files /dev/null and b/favicon/mstile-150x150.png differ diff --git a/favicon/mstile-310x150.png b/favicon/mstile-310x150.png new file mode 100644 index 00000000..6dcf8af0 Binary files /dev/null and b/favicon/mstile-310x150.png differ diff --git a/favicon/mstile-310x310.png b/favicon/mstile-310x310.png new file mode 100644 index 00000000..c93cc9e1 Binary files /dev/null and b/favicon/mstile-310x310.png differ diff --git a/favicon/mstile-70x70.png b/favicon/mstile-70x70.png new file mode 100644 index 00000000..d2e06ef6 Binary files /dev/null and b/favicon/mstile-70x70.png differ diff --git a/favicon/safari-pinned-tab.svg b/favicon/safari-pinned-tab.svg new file mode 100644 index 00000000..a0b0fb4c --- /dev/null +++ b/favicon/safari-pinned-tab.svg @@ -0,0 +1,27 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + 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" +} -- cgit v1.2.3 From a053413e98c057f7a7a3d85b3c3ba72f2e0ccb55 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 25 Apr 2019 21:01:35 -0700 Subject: Move favicon back to folder --- _layouts/default.html | 2 +- favicon.ico | Bin 15086 -> 0 bytes favicon/favicon.ico | Bin 0 -> 15086 bytes 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 favicon.ico create mode 100644 favicon/favicon.ico diff --git a/_layouts/default.html b/_layouts/default.html index f8b9f991..28202d28 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -15,7 +15,7 @@ - + diff --git a/favicon.ico b/favicon.ico deleted file mode 100644 index ec0bf22c..00000000 Binary files a/favicon.ico and /dev/null differ diff --git a/favicon/favicon.ico b/favicon/favicon.ico new file mode 100644 index 00000000..ec0bf22c Binary files /dev/null and b/favicon/favicon.ico differ -- cgit v1.2.3 From 4e1a32bf33f7ee8cf9a125440ed11db61f884a88 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 25 Apr 2019 21:14:26 -0700 Subject: Remove ga --- _layouts/default.html | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/_layouts/default.html b/_layouts/default.html index 28202d28..c4972e3e 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -64,15 +64,5 @@ - {% if site.google_analytics %} - -{% endif %} \ No newline at end of file -- cgit v1.2.3 From 5139111a7f1f4b18e993b23d2a0b7bb5a260e905 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 26 Apr 2019 23:48:19 -0700 Subject: Docker (#1411) * Add initial docker test * Depend on java only * Remove android part * Move build stuff to docker * Use shorter docker file * Quiet docker build * Move quiet flag forward * Export android home * Echo versions * Try generic lang * Copy project * Group sdk manager runs * Reorder sdkmanager * Gitignore generated files * Copy apk output out of docker * Fail if no apks found * Install packages * Add caching * Name container * Update caching * Add package lock file * Update folder path * Switch home dir * Copy folder contents * Disable caching * Add back gradle caching * Remove original files from asset folder * Try generic docker * Delete extra loader * Try java * Try android * Use java * Restrict caching --- .travis.yml | 43 +- Dockerfile | 49 + app/src/web/.gitignore | 4 +- app/src/web/.idea/watcherTasks.xml | 4 +- app/src/web/assets/css/core/_base.scss | 107 -- app/src/web/assets/css/core/_colors.scss | 18 - app/src/web/assets/css/core/_core_bg.scss | 86 -- app/src/web/assets/css/core/_core_border.scss | 94 -- app/src/web/assets/css/core/_core_messenger.scss | 20 - app/src/web/assets/css/core/_core_text.scss | 40 - app/src/web/assets/css/core/_main.scss | 6 - app/src/web/assets/css/core/_svg.scss | 74 - app/src/web/assets/css/core/core.css | 307 ---- app/src/web/assets/css/core/core.scss | 54 - app/src/web/assets/css/themes/.gitignore | 2 - app/src/web/assets/css/themes/custom.css | 339 ---- app/src/web/assets/css/themes/custom.scss | 14 - app/src/web/assets/css/themes/material_amoled.css | 339 ---- app/src/web/assets/css/themes/material_amoled.scss | 11 - app/src/web/assets/css/themes/material_dark.css | 339 ---- app/src/web/assets/css/themes/material_dark.scss | 10 - app/src/web/assets/css/themes/material_glass.css | 339 ---- app/src/web/assets/css/themes/material_glass.scss | 10 - app/src/web/assets/css/themes/material_light.css | 339 ---- app/src/web/assets/css/themes/material_light.scss | 15 - app/src/web/assets/js/click_a.js | 46 - app/src/web/assets/js/click_a.ts | 57 - app/src/web/assets/js/click_debugger.js | 12 - app/src/web/assets/js/click_debugger.ts | 15 - app/src/web/assets/js/context_a.js | 98 -- app/src/web/assets/js/context_a.ts | 125 -- app/src/web/assets/js/document_watcher.js | 23 - app/src/web/assets/js/document_watcher.ts | 27 - app/src/web/assets/js/header_badges.js | 7 - app/src/web/assets/js/header_badges.ts | 7 - app/src/web/assets/js/media.js | 41 - app/src/web/assets/js/media.ts | 47 - app/src/web/assets/js/menu.js | 55 - app/src/web/assets/js/menu.ts | 59 - app/src/web/assets/js/notif_msg.js | 25 - app/src/web/assets/js/notif_msg.ts | 25 - app/src/web/assets/js/textarea_listener.js | 23 - app/src/web/assets/js/textarea_listener.ts | 31 - app/src/web/assets/typings/frost.d.ts | 27 - app/src/web/package-lock.json | 1630 ++++++++++++++++++++ app/src/web/package.json | 6 +- app/src/web/scss/core/_base.scss | 107 ++ app/src/web/scss/core/_colors.scss | 18 + app/src/web/scss/core/_core_bg.scss | 86 ++ app/src/web/scss/core/_core_border.scss | 94 ++ app/src/web/scss/core/_core_messenger.scss | 20 + app/src/web/scss/core/_core_text.scss | 40 + app/src/web/scss/core/_main.scss | 6 + app/src/web/scss/core/_svg.scss | 74 + app/src/web/scss/core/core.scss | 54 + app/src/web/scss/themes/.gitignore | 1 + app/src/web/scss/themes/custom.scss | 14 + app/src/web/scss/themes/material_amoled.scss | 11 + app/src/web/scss/themes/material_dark.scss | 10 + app/src/web/scss/themes/material_glass.scss | 10 + app/src/web/scss/themes/material_light.scss | 15 + app/src/web/ts/click_a.ts | 57 + app/src/web/ts/click_debugger.ts | 15 + app/src/web/ts/context_a.ts | 125 ++ app/src/web/ts/document_watcher.ts | 27 + app/src/web/ts/header_badges.ts | 7 + app/src/web/ts/media.ts | 47 + app/src/web/ts/menu.ts | 59 + app/src/web/ts/notif_msg.ts | 25 + app/src/web/ts/textarea_listener.ts | 31 + app/src/web/tsconfig.json | 8 +- app/src/web/typings/frost.d.ts | 27 + docker_build.sh | 12 + generate-apk-release.sh | 11 +- gradle.properties | 2 + 75 files changed, 2710 insertions(+), 3352 deletions(-) create mode 100644 Dockerfile delete mode 100644 app/src/web/assets/css/core/_base.scss delete mode 100644 app/src/web/assets/css/core/_colors.scss delete mode 100644 app/src/web/assets/css/core/_core_bg.scss delete mode 100644 app/src/web/assets/css/core/_core_border.scss delete mode 100644 app/src/web/assets/css/core/_core_messenger.scss delete mode 100644 app/src/web/assets/css/core/_core_text.scss delete mode 100644 app/src/web/assets/css/core/_main.scss delete mode 100644 app/src/web/assets/css/core/_svg.scss delete mode 100644 app/src/web/assets/css/core/core.css delete mode 100644 app/src/web/assets/css/core/core.scss delete mode 100644 app/src/web/assets/css/themes/.gitignore delete mode 100644 app/src/web/assets/css/themes/custom.css delete mode 100644 app/src/web/assets/css/themes/custom.scss delete mode 100644 app/src/web/assets/css/themes/material_amoled.css delete mode 100644 app/src/web/assets/css/themes/material_amoled.scss delete mode 100644 app/src/web/assets/css/themes/material_dark.css delete mode 100644 app/src/web/assets/css/themes/material_dark.scss delete mode 100644 app/src/web/assets/css/themes/material_glass.css delete mode 100644 app/src/web/assets/css/themes/material_glass.scss delete mode 100644 app/src/web/assets/css/themes/material_light.css delete mode 100644 app/src/web/assets/css/themes/material_light.scss delete mode 100644 app/src/web/assets/js/click_a.js delete mode 100644 app/src/web/assets/js/click_a.ts delete mode 100644 app/src/web/assets/js/click_debugger.js delete mode 100644 app/src/web/assets/js/click_debugger.ts delete mode 100644 app/src/web/assets/js/context_a.js delete mode 100644 app/src/web/assets/js/context_a.ts delete mode 100644 app/src/web/assets/js/document_watcher.js delete mode 100644 app/src/web/assets/js/document_watcher.ts delete mode 100644 app/src/web/assets/js/header_badges.js delete mode 100644 app/src/web/assets/js/header_badges.ts delete mode 100644 app/src/web/assets/js/media.js delete mode 100644 app/src/web/assets/js/media.ts delete mode 100644 app/src/web/assets/js/menu.js delete mode 100644 app/src/web/assets/js/menu.ts delete mode 100644 app/src/web/assets/js/notif_msg.js delete mode 100644 app/src/web/assets/js/notif_msg.ts delete mode 100644 app/src/web/assets/js/textarea_listener.js delete mode 100644 app/src/web/assets/js/textarea_listener.ts delete mode 100644 app/src/web/assets/typings/frost.d.ts create mode 100644 app/src/web/package-lock.json create mode 100644 app/src/web/scss/core/_base.scss create mode 100644 app/src/web/scss/core/_colors.scss create mode 100644 app/src/web/scss/core/_core_bg.scss create mode 100644 app/src/web/scss/core/_core_border.scss create mode 100644 app/src/web/scss/core/_core_messenger.scss create mode 100644 app/src/web/scss/core/_core_text.scss create mode 100644 app/src/web/scss/core/_main.scss create mode 100644 app/src/web/scss/core/_svg.scss create mode 100644 app/src/web/scss/core/core.scss create mode 100644 app/src/web/scss/themes/.gitignore create mode 100644 app/src/web/scss/themes/custom.scss create mode 100644 app/src/web/scss/themes/material_amoled.scss create mode 100644 app/src/web/scss/themes/material_dark.scss create mode 100644 app/src/web/scss/themes/material_glass.scss create mode 100644 app/src/web/scss/themes/material_light.scss create mode 100644 app/src/web/ts/click_a.ts create mode 100644 app/src/web/ts/click_debugger.ts create mode 100644 app/src/web/ts/context_a.ts create mode 100644 app/src/web/ts/document_watcher.ts create mode 100644 app/src/web/ts/header_badges.ts create mode 100644 app/src/web/ts/media.ts create mode 100644 app/src/web/ts/menu.ts create mode 100644 app/src/web/ts/notif_msg.ts create mode 100644 app/src/web/ts/textarea_listener.ts create mode 100644 app/src/web/typings/frost.d.ts create mode 100755 docker_build.sh mode change 100644 => 100755 generate-apk-release.sh diff --git a/.travis.yml b/.travis.yml index b8d8f1bd..a2335bd9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,32 +1,23 @@ -language: android -jdk: -- oraclejdk8 -android: - components: - - tools - - platform-tools - - build-tools-28.0.3 - - android-28 - - extra-android-support - - extra-android-m2repository - - extra-google-m2repository - licenses: - - ".+" +language: java +services: +- docker git: depth: 500 before_install: - openssl aes-256-cbc -K $encrypted_0454d0cf846c_key -iv $encrypted_0454d0cf846c_iv -in files/frost.tar.enc -out files/frost.tar -d - tar xvf files/frost.tar -- yes | sdkmanager "platforms;android-28" +- docker build -q -t frost . +- docker volume create -o device=$HOME/.gradle/caches/ -o o=bind gradle_caches +- docker volume create -o device=$HOME/.gradle/wrapper/ -o o=bind gradle_wrapper +install: true after_success: -- chmod +x ./generate-apk-release.sh; ./generate-apk-release.sh +- ./generate-apk-release.sh script: -- cd $TRAVIS_BUILD_DIR/ -- printf "Starting script\n" -- chmod +x gradlew -- "./gradlew --quiet androidGitVersion" -- "./gradlew lintReleaseTest testReleaseUnitTest assembleReleaseTest" +- cd $TRAVIS_BUILD_DIR +- docker run --name frost_container -v gradle_caches:/root/.gradle/caches/ -v gradle_wrapper:/root/.gradle/wrapper/ frost +- mkdir $HOME/Frost +- docker cp frost_container:/frost/app/build/outputs/apk/releaseTest/. $HOME/Frost notifications: email: false slack: @@ -46,14 +37,12 @@ branches: - master - l10n_dev before_cache: -- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock -- rm -fr $HOME/.gradle/caches/*/plugin-resolution/ + - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock + - rm -rf $HOME/.gradle/caches/*/plugin-resolution/ cache: directories: - - "$HOME/.gradle/caches/" - - "$HOME/.gradle/wrapper/" - - "$HOME/.android/build-cache" - - "$HOME/.m2s" + - $HOME/.gradle/caches/ + - $HOME/.gradle/wrapper/ env: global: - secure: X3J97ccW+8K0bXPXhX608vPx7Pr/G4ju7quxydqMaYGgClHxoL/WpXOBAyyllde5P28PY4kioaqcI21BEhnAw0QUbmnzVLA1Qd5VS7aMPHpEnInKuOxGZ2d570OZd1f+ozFVt05vzG0VBJlBAkVhz2GWNxQdmIV1sO28MH526JMuYaEREuuywVSZmAeY7AAbW9MeCC2wvHvNmhk2nk6NLRQcsrDHcBsimy9fnnQ9lT/QsvToi1ZJd/MN7YkGDUULR+YmaotBzG546UJ1EiZQX91bFEJfP0oL43Pk7t5snzmHnKjLOr8Mt5QsIUXaiy/uzhUVmuDh1i0GEpZmhqM7nz/T6P7ogaLbbyJeauNmf15nu+e3hSvNiTzKyIwfSSflv8Do3g8/Eo3dKfIi3I8/OKF/uZ76kywh2LRqtZAqxRDiAMDZVwsRgD4aztoWm5AWa3tSoGy1J7i1eoqX6bNqokRbjgheTqcjN13kCdSZi3pZX7UBYm2Vumhn4izhTume19Rh9SqTmRgQ8jM7ynxHh7vVsJPPJG0HbQ623xz+d9mtXGy1fAb0dcUJMXdOhFN3m6AnKuHiF7cmsqje7Euk/TOZyqZmu0xEhTkugMbNKwGrklJiwRr3IoLtPdhLE38u3/auloUqBQ4K/iA9ZdhAreTSHEaI9d3J4N6kqCj3U30= diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..a8fc1e07 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,49 @@ +FROM openjdk:8 + +# Android SDK + +ENV ANDROID_HOME /opt/android-sdk-linux + +# Download Android SDK into $ANDROID_HOME +# You can find URL to the current version at: https://developer.android.com/studio/index.html +# Or https://github.com/Homebrew/homebrew-cask/blob/master/Casks/android-sdk.rb + +RUN mkdir -p ${ANDROID_HOME} && \ + cd ${ANDROID_HOME} && \ + wget -q https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip -O android_tools.zip && \ + unzip android_tools.zip && \ + rm android_tools.zip + +ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools + +# Accept Android SDK licenses && install other elements +# For full list; see sdkmanager --list --verbose +RUN yes | sdkmanager --licenses && \ + sdkmanager 'platform-tools' && \ + sdkmanager 'extras;google;m2repository' && \ + sdkmanager 'extras;android;m2repository' + +# SDK Specific + +RUN sdkmanager 'platforms;android-28' && \ + sdkmanager 'build-tools;28.0.3' + +# Install Node.js + +ENV NODEJS_VERSION=11.12.0 \ + PATH=$PATH:/opt/node/bin + +WORKDIR "/opt/node" + +RUN apt-get update && apt-get install -y curl git ca-certificates --no-install-recommends && \ + curl -sL https://nodejs.org/dist/v${NODEJS_VERSION}/node-v${NODEJS_VERSION}-linux-x64.tar.gz | tar xz --strip-components=1 && \ + rm -rf /var/lib/apt/lists/* && \ + apt-get clean + +RUN mkdir -p /frost/ + +WORKDIR /frost/ + +COPY . /frost/ + +CMD ["./docker_build.sh"] \ No newline at end of file diff --git a/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 @@ - diff --git a/app/src/web/assets/css/core/_base.scss b/app/src/web/assets/css/core/_base.scss deleted file mode 100644 index 472319fe..00000000 --- a/app/src/web/assets/css/core/_base.scss +++ /dev/null @@ -1,107 +0,0 @@ -@mixin placeholder { - ::-webkit-input-placeholder { - @content; - } - - :-moz-placeholder { - @content; - } - - ::-moz-placeholder { - @content; - } - - :-ms-input-placeholder { - @content; - } -} - -@mixin keyframes($name) { - @-webkit-keyframes #{$name} { - @content; - } - - @-moz-keyframes #{$name} { - @content; - } - - //@-ms-keyframes #{$name} { - // @content; - //} - - @keyframes #{$name} { - @content; - } -} - -// Helper function to replace characters in a string -@function str-replace($string, $search, $replace: "") { - $index: str-index($string, $search); - - @return if($index, str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace), $string); -} - -// https://css-tricks.com/probably-dont-base64-svg/ -// SVG optimization thanks to https://codepen.io/jakob-e/pen/doMoML -// Function to create an optimized svg url -// Version: 1.0.6 -@function svg-url($svg) { - // - // Add missing namespace - // - @if not str-index($svg, xmlns) { - $svg: str-replace($svg, "", "%3E"); - - // - // The maybe list - // - // Keep size and compile time down - // ... only add on documented fail - // - // $chunk: str-replace($chunk, '&', '%26'); - // $chunk: str-replace($chunk, '|', '%7C'); - // $chunk: str-replace($chunk, '[', '%5B'); - // $chunk: str-replace($chunk, ']', '%5D'); - // $chunk: str-replace($chunk, '^', '%5E'); - // $chunk: str-replace($chunk, '`', '%60'); - // $chunk: str-replace($chunk, ';', '%3B'); - // $chunk: str-replace($chunk, '?', '%3F'); - // $chunk: str-replace($chunk, ':', '%3A'); - // $chunk: str-replace($chunk, '@', '%40'); - // $chunk: str-replace($chunk, '=', '%3D'); - - $encoded: #{$encoded}#{$chunk}; - $index: $index + $slice; - } - - @return url("data:image/svg+xml,#{$encoded}"); -} - -// Background svg mixin -@mixin background-svg($svg, $extra: "no-repeat") { - background: svg-url($svg) unquote($extra) !important; -} diff --git a/app/src/web/assets/css/core/_colors.scss b/app/src/web/assets/css/core/_colors.scss deleted file mode 100644 index 7610572c..00000000 --- a/app/src/web/assets/css/core/_colors.scss +++ /dev/null @@ -1,18 +0,0 @@ -$bg_transparent: rgba(#f0f, 0.02) !default; - -//Keep above as first line so partials aren't compiled -//Our default colors are test colors; production files should always import the actual colors - -$text: #d7b0d7 !default; -// must be visible with accent as the background -$accent_text: #76d7c2 !default; -$link: #9266d5 !default; -$accent: #980008 !default; -$background: #451515 !default; -// background2 must be transparent -$background2: rgba(lighten($background, 35%), 0.35) !default; //Also change ratio in material_light -$bg_opaque: rgba($background, 1.0) !default; -$bg_opaque2: rgba($background2, 1.0) !default; -$card: #239645 !default; -$tint: #ff4682 !default; // must be different from $background -$divider: rgba($text, 0.3) !default; diff --git a/app/src/web/assets/css/core/_core_bg.scss b/app/src/web/assets/css/core/_core_bg.scss deleted file mode 100644 index 494ee0c1..00000000 --- a/app/src/web/assets/css/core/_core_bg.scss +++ /dev/null @@ -1,86 +0,0 @@ -#viewport { - background: $background !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: $bg_transparent !important; -} - -//card related -._31nf, ._2v9s, ._d4i, article._55wo, ._10c_, ._2jl2, ._6150, ._50mi, ._4-dw, ._4_2z, ._5m_s, ._13fn { - background: $card !important; -} - -// unread related - -.aclb { - background: $tint !important; -} - -//contains images so must have background-color -._cv_, ._2sq8 { - background-color: $bg_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: $bg_opaque !important; -} - -._403n, ._14v5 ._14v8, ._1-kc { - background: $bg_opaque2 !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: $background2 !important; -} - -.mQuestionsPollResultsBar .shaded { - background: $accent !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: $divider !important; -} - -//fab -button ._v89 ._54k8._1fl1 { - background: $accent !important; -} diff --git a/app/src/web/assets/css/core/_core_border.scss b/app/src/web/assets/css/core/_core_border.scss deleted file mode 100644 index 9f2bdec0..00000000 --- a/app/src/web/assets/css/core/_core_border.scss +++ /dev/null @@ -1,94 +0,0 @@ -//border between like and comment -._15kl::before, ._37fd .inlineComposerButton, ._1hb:before, -._5j35::after, ._2k4b, ._3to7, ._4nw8 { - border-left: 1px solid $divider !important; -} - -._4_d1, ._5cni, ._3jcq { - border-right: 1px solid $divider !important; -} - -//above see more -._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 $divider !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 $divider !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 $divider !important; - border-bottom: 1px solid $divider !important; -} - -//friend card border -._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 $divider !important; -} - -.mQuestionsPollResultsBar .shaded, ._1027._13sm { - border: 1px solid $text !important; -} - -._3gka { - border: 1px dashed $divider !important; -} - -//link card bottom border -._4o58::after, .acr, ._t21, ._2bdb, -.acw, .aclb, ._4qax, ._5h8f { - border-color: $divider !important; -} - -// like, comment, share divider -._15ks ._15kl::before { - border-left: 1px solid transparent !important; -} - -._56bf, .touch .btn { - border-radius: 0 !important; - border: 0 !important; -} - -//page side tab layout -._2cis { - border-left: 10px solid $bg_transparent !important; - border-right: 10px solid $bg_transparent !important; -} - -._2cir.selected, ._42rv, ._5zma, ._2x2s { - border-bottom: 3px solid $text !important; -} - -._1ss6 { - border-left: 2px solid $text !important; -} - -._484w.selected > ._6zf, ._5kqs::after, ._3lvo ._5xum._5xuk, ._x0b { - border-bottom: 1px solid $text !important; -} diff --git a/app/src/web/assets/css/core/_core_messenger.scss b/app/src/web/assets/css/core/_core_messenger.scss deleted file mode 100644 index 608fc23d..00000000 --- a/app/src/web/assets/css/core/_core_messenger.scss +++ /dev/null @@ -1,20 +0,0 @@ -// Not all messenger related components are here; only the main ones. -// Borders for instance are merged into core_border - -// Other person's message bubble -._34ee { - background: $background2 !important; - color: $text !important; - -} - -// Your message bubble; order matters -._34em ._34ee { - background: $accent !important; - color: $accent_text !important; -} - -// Sticker page -._5as0, ._5cni, ._5as2 { - background: $bg_opaque !important; -} \ No newline at end of file diff --git a/app/src/web/assets/css/core/_core_text.scss b/app/src/web/assets/css/core/_core_text.scss deleted file mode 100644 index 63622610..00000000 --- a/app/src/web/assets/css/core/_core_text.scss +++ /dev/null @@ -1,40 +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: $text !important; -} - -strong > a, ._15ks ._2q8z._2q8z, ._1e3e, .blueName, ._5kqs ._55sr { - color: $accent !important; -} - -._42nf ._42ng { - color: transparent !important; -} - -// most links do not have a special color. We will highlight those in posts and messages -p > a, .msg span > a { - color: $link !important; -} diff --git a/app/src/web/assets/css/core/_main.scss b/app/src/web/assets/css/core/_main.scss deleted file mode 100644 index 3e972f93..00000000 --- a/app/src/web/assets/css/core/_main.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import "core"; -@import "svg"; - -//this file is used as the base for all themes -//given that svgs take a lot of characters, we won't compile them when testing -//therefore we use the core scss diff --git a/app/src/web/assets/css/core/_svg.scss b/app/src/web/assets/css/core/_svg.scss deleted file mode 100644 index 8c714438..00000000 --- a/app/src/web/assets/css/core/_svg.scss +++ /dev/null @@ -1,74 +0,0 @@ -// icons courtesy of https://material.io/icons/ - -$camera: ''; - -// status upload image -._50uu { - @include background-svg($camera); -} - -$video: ''; - -// status upload video -._50uw { - @include background-svg($video); -} - -$like: ''; -$like_selected: ''; - -// 2018/12/29 -// Previously ._15km ._15ko::before and ._15km ._15ko._77la::before; however, reaction changes no longer affect this element -// The robust measure seems to be the parent of a[data-sigil~="like-reaction-flyout"] along with [data-sigil~="like"] for an unliked post -// and [data-sigil~="unlike"] for a liked post -._15km ._15ko::before { - @include background-svg($like); - background-position: center !important; -} - -._15km ._15ko._77la::before { - @include background-svg($like_selected); - background-position: center !important; -} - -$comment: ''; - -._15km ._15kq::before { - @include background-svg($comment); - background-position: center !important; -} - -$share: ''; - -._15km ._15kr::before { - @include background-svg($share); - background-position: center !important; -} - -$more_horiz: ''; - -//$menus: ".sp_89zNula0Qh5", -//".sp_MP2OtCXORz9", -//".sp_NIWBacTn8LF", -//// 2018/12/31 -//".sp_9ZFVhnFyWsw", -//// 2019/01/03 -//".sp_SJIJjSlGEIO"; -// -//$menu_collector: (); -// -//@each $menu in $menus { -// $menu_collector: append($menu_collector, unquote('#{$menu}'), 'comma'); -// $menu_collector: append($menu_collector, unquote('#{$menu}_2x'), 'comma'); -// $menu_collector: append($menu_collector, unquote('#{$menu}_3x'), 'comma'); -//} -// -//#{$menu_collector} { -// @include background-svg($more_horiz); -// background-position: center !important; -//} - -.story_body_container i.img[data-sigil*="story-popup-context"] { - @include background-svg($more_horiz); - background-position: center !important; -} \ No newline at end of file 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/core/core.scss b/app/src/web/assets/css/core/core.scss deleted file mode 100644 index 38086529..00000000 --- a/app/src/web/assets/css/core/core.scss +++ /dev/null @@ -1,54 +0,0 @@ -@import "colors"; -@import "base"; -@import "core_text"; -@import "core_bg"; -@import "core_border"; -@import "core_messenger"; - -//GLOBAL overrides; use with caution -*, *::after, *::before { - text-shadow: none !important; - box-shadow: none !important; -} - -// .touch .btnS, button, ._94v, ._590n { -// box-shadow: none !important; -// } - -[data-sigil=m_login_upsell], -[data-sigil="m-loading-indicator-animate m-loading-indicator-root"] { - display: none !important; -} - -@include placeholder { - color: $text !important; -} - -.excessItem { - outline: $divider !important; -} - -._3m1m { - background: linear-gradient(transparent, $bg_opaque) !important; -} - -//new comment -@include keyframes(highlightFade) { - 0%, 50% { - background: $background2; - } - - 100% { - background: $bg_transparent; - } -} - -@include keyframes(chatHighlightAnimation) { - 0%, 100% { - background: $bg_transparent; - } - - 50% { - background: $background2; - } -} diff --git a/app/src/web/assets/css/themes/.gitignore b/app/src/web/assets/css/themes/.gitignore deleted file mode 100644 index 01d06441..00000000 --- a/app/src/web/assets/css/themes/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -test.scss -test.css \ No newline at end of file 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/custom.scss b/app/src/web/assets/css/themes/custom.scss deleted file mode 100644 index 50c029fb..00000000 --- a/app/src/web/assets/css/themes/custom.scss +++ /dev/null @@ -1,14 +0,0 @@ -$bg_transparent: unquote('$BT$'); -$text: unquote('$T$'); -$link: unquote('$TT$'); -$accent: unquote('$A$'); -$accent_text: unquote('$AT$'); -$background: unquote('$B$'); -$background2: unquote('$BBT$'); -$bg_opaque: unquote('$O$'); -$bg_opaque2: unquote('$OO$'); -$divider: unquote('$D$'); -$card: unquote('$C$'); -$tint: unquote('$TI$'); - -@import "../core/main"; 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_amoled.scss b/app/src/web/assets/css/themes/material_amoled.scss deleted file mode 100644 index 19190126..00000000 --- a/app/src/web/assets/css/themes/material_amoled.scss +++ /dev/null @@ -1,11 +0,0 @@ -$text: #fff; -$accent_text: #fff; -$link: #5d86dd; -$accent: #5d86dd; -$background: #000; -$background2: rgba($background, 0.35); -$bg_transparent: $background; -$card: $background2; -$tint: rgba(#fff, 0.2); - -@import "../core/main"; 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_dark.scss b/app/src/web/assets/css/themes/material_dark.scss deleted file mode 100644 index 18b8b461..00000000 --- a/app/src/web/assets/css/themes/material_dark.scss +++ /dev/null @@ -1,10 +0,0 @@ -$text: #fff; -$accent_text: #fff; -$link: #5d86dd; -$accent: #5d86dd; -$background: #303030; -$bg_transparent: $background; -$card: #353535; -$tint: rgba(#fff, 0.2); - -@import "../core/main"; 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_glass.scss b/app/src/web/assets/css/themes/material_glass.scss deleted file mode 100644 index 0c61a38c..00000000 --- a/app/src/web/assets/css/themes/material_glass.scss +++ /dev/null @@ -1,10 +0,0 @@ -$text: #fff; -$accent_text: #fff; -$link: #5d86dd; -$accent: #5d86dd; -$background: rgba(#000, 0.1); -$bg_transparent: transparent; -$card: rgba(#000, 0.25); -$tint: rgba(#fff, 0.15); - -@import "../core/main"; 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/css/themes/material_light.scss b/app/src/web/assets/css/themes/material_light.scss deleted file mode 100644 index 7ec58463..00000000 --- a/app/src/web/assets/css/themes/material_light.scss +++ /dev/null @@ -1,15 +0,0 @@ -$text: #000; -$accent_text: #fff; -$link: #3b5998; -$accent: #3b5998; -$background: #fafafa; -// this is actually the inverse of material light (bg should be gray, cards should be white), -// but it looks better than the alternative -$background2: rgba(darken($background, 8%), 0.35); - -$bg_transparent: $background; - -$card: #fff; -$tint: #ddd; - -@import "../core/main"; \ No newline at end of file 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_a.ts b/app/src/web/assets/js/click_a.ts deleted file mode 100644 index 5023610e..00000000 --- a/app/src/web/assets/js/click_a.ts +++ /dev/null @@ -1,57 +0,0 @@ -(function () { - let prevented = false; - - const _frostAClick = (e: Event) => { - // check for valid target - const target = e.target || e.currentTarget || e.srcElement; - if (!(target instanceof Element)) { - console.log("No element found"); - return - } - let element: Element = target; - // Notifications are two layers under - for (let i = 0; i < 2; i++) { - if (element.tagName !== 'A') { - element = element.parentElement; - } - } - if (element.tagName === 'A') { - if (!prevented) { - const url = element.getAttribute('href'); - if (!url || url === '#') { - return - } - console.log(`Click intercept ${url}`); - // If Frost is injected, check if loading the url through an overlay works - if (Frost.loadUrl(url)) { - e.stopPropagation(); - e.preventDefault(); - } - } else { - console.log("Click intercept prevented") - } - } - }; - - /* - * On top of the click event, we must stop it for long presses - * Since that will conflict with the context menu - * Note that we only override it on conditions where the context menu - * Will occur - */ - const _frostPreventClick = () => { - console.log("Click _frostPrevented"); - prevented = true; - }; - - document.addEventListener('click', _frostAClick, true); - let clickTimeout: number | undefined = undefined; - document.addEventListener('touchstart', () => { - clickTimeout = setTimeout(_frostPreventClick, 400); - }, true); - document.addEventListener('touchend', () => { - 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/click_debugger.ts b/app/src/web/assets/js/click_debugger.ts deleted file mode 100644 index 088271fa..00000000 --- a/app/src/web/assets/js/click_debugger.ts +++ /dev/null @@ -1,15 +0,0 @@ -// For desktop only - -(function () { - const _frostAContext = (e: Event) => { - // Commonality; check for valid target - const 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/context_a.ts b/app/src/web/assets/js/context_a.ts deleted file mode 100644 index 5eec7611..00000000 --- a/app/src/web/assets/js/context_a.ts +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Context menu for links - * Largely mimics click_a.js - */ - -(function () { - let longClick = false; - - /** - * Given event and target, return true if handled and false otherwise. - */ - type EventHandler = (e: Event, target: HTMLElement) => Boolean - - const _frostCopyComment: EventHandler = (e, target) => { - if (!target.hasAttribute('data-commentid')) { - return false; - } - const text = target.innerText; - console.log(`Copy comment ${text}`); - Frost.contextMenu(null, text); - return true; - }; - - /** - * Posts should click a tag, with two parents up being div.story_body_container - */ - const _frostCopyPost: EventHandler = (e, target) => { - if (target.tagName !== 'A') { - return false; - } - const parent1 = target.parentElement; - if (!parent1 || parent1.tagName !== 'DIV') { - return false; - } - const parent2 = parent1.parentElement; - if (!parent2 || !parent2.classList.contains('story_body_container')) { - return false; - } - const url = target.getAttribute('href'); - const text = parent1.innerText; - console.log(`Copy post ${url} ${text}`); - Frost.contextMenu(url, text); - return true; - }; - - const _getImageStyleUrl = (el: Element): string | null => { - const img = el.querySelector("[style*=\"background-image: url(\"]"); - if (!img) { - return null - } - return (window.getComputedStyle(img, null).backgroundImage).trim().slice(4, -1); - }; - - /** - * Opens image activity for posts with just one image - */ - const _frostImage: EventHandler = (e, target) => { - let element: Element = target; - // Notifications are two layers under - for (let i = 0; i < 2; i++) { - if (element.tagName !== 'A') { - element = element.parentElement; - } else { - break - } - } - if (element.tagName !== 'A') { - return false; - } - const url = element.getAttribute('href'); - if (!url || url === '#') { - return false; - } - const text = (element.parentElement).innerText; - // Check if image item exists, first in children and then in parent - const imageUrl = _getImageStyleUrl(element) || _getImageStyleUrl(element.parentElement); - if (imageUrl) { - console.log(`Context image: ${imageUrl}`); - Frost.loadImage(imageUrl, text); - return true; - } - // Check if true img exists - const img = element.querySelector("img[src*=scontent]"); - if (img instanceof HTMLMediaElement) { - const 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; - }; - - const handlers = [_frostImage, _frostCopyComment, _frostCopyPost]; - - const _frostAContext = (e: Event) => { - Frost.longClick(true); - longClick = true; - - /* - * Commonality; check for valid target - */ - const target = e.target || e.currentTarget || e.srcElement; - if (!(target instanceof HTMLElement)) { - console.log("No element found"); - return - } - for (const h of handlers) { - if (h(e, target)) { - e.stopPropagation(); - e.preventDefault(); - return - } - } - }; - - document.addEventListener('contextmenu', _frostAContext, true); - document.addEventListener('touchend', () => { - 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/document_watcher.ts b/app/src/web/assets/js/document_watcher.ts deleted file mode 100644 index e671149c..00000000 --- a/app/src/web/assets/js/document_watcher.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Emit key once half the viewport is covered -(function () { - const isReady = () => { - return document.body.scrollHeight > innerHeight + 100 - }; - - if (isReady()) { - console.log('Already ready'); - Frost.isReady(); - return - } - - console.log('Injected document watcher'); - - const observer = new MutationObserver(() => { - 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/header_badges.ts b/app/src/web/assets/js/header_badges.ts deleted file mode 100644 index 473749f2..00000000 --- a/app/src/web/assets/js/header_badges.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Fetches the header contents if it exists -(function() { - const 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/media.ts b/app/src/web/assets/js/media.ts deleted file mode 100644 index 5b9b1a54..00000000 --- a/app/src/web/assets/js/media.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Handles media events -(function () { - const _frostMediaClick = (e: Event) => { - const target = e.target || e.srcElement; - if (!(target instanceof HTMLElement)) { - return - } - let element: HTMLElement = target; - const dataset = element.dataset; - if (!dataset || !dataset.sigil || dataset.sigil.toLowerCase().indexOf('inlinevideo') == -1) { - return - } - let i = 0; - while (!element.hasAttribute('data-store')) { - if (++i > 2) { - return - } - element = element.parentNode; - } - const store = element.dataset.store; - if (!store) { - return - } - - let dataStore; - - try { - dataStore = JSON.parse(store) - } catch (e) { - return - } - - const url = dataStore.src; - - // !startsWith; see https://stackoverflow.com/a/36876507/4407321 - 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/menu.ts b/app/src/web/assets/js/menu.ts deleted file mode 100644 index 6f9dbf16..00000000 --- a/app/src/web/assets/js/menu.ts +++ /dev/null @@ -1,59 +0,0 @@ -// Click menu and move contents to main view -(function () { - const viewport = document.querySelector("#viewport"); - const root = document.querySelector("#root"); - const bookmarkJewel = document.querySelector("#bookmarks_jewel"); - if (!viewport || !root || !bookmarkJewel) { - console.log('Menu.js: main elements not found'); - Frost.emit(0); - return - } - const menuA = bookmarkJewel.querySelector("a"); - if (!menuA) { - console.log('Menu.js: menu links not found'); - Frost.emit(0); - return - } - const jewel = document.querySelector('#mJewelNav'); - if (!jewel) { - console.log('Menu.js: jewel is null'); - return - } - - const y = new MutationObserver(() => { - viewport.removeAttribute('style'); - root.removeAttribute('style'); - }); - - y.observe(viewport, { - attributes: true - }); - y.observe(root, { - attributes: true - }); - - const x = new MutationObserver(() => { - const menu = document.querySelector('.mSideMenu'); - if (menu) { - x.disconnect(); - console.log("Found side menu"); - // Transfer elements - while (root.firstChild) { - root.removeChild(root.firstChild); - } - while (menu.childNodes.length) { - viewport.appendChild(menu.childNodes[0]); - } - Frost.emit(0); - setTimeout(() => { - 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/notif_msg.ts b/app/src/web/assets/js/notif_msg.ts deleted file mode 100644 index b7ce7a19..00000000 --- a/app/src/web/assets/js/notif_msg.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Binds callback to an invisible webview to take in the search events -(function () { - let finished = false; - const x = new MutationObserver(() => { - const _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(() => { - 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/assets/js/textarea_listener.ts b/app/src/web/assets/js/textarea_listener.ts deleted file mode 100644 index 062f5bf6..00000000 --- a/app/src/web/assets/js/textarea_listener.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * focus listener for textareas - * since swipe to refresh is quite sensitive, we will disable it - * when we detect a user typing - * note that this extends passed having a keyboard opened, - * as a user may still be reviewing his/her post - * swiping should automatically be reset on refresh - */ -(function () { - const _frostFocus = (e: Event) => { - const element = e.target || e.srcElement; - if (!(element instanceof Element)) { - return - } - console.log(`FrostJSI focus, ${element.tagName}`); - if (element.tagName === 'TEXTAREA') { - Frost.disableSwipeRefresh(true); - } - }; - - const _frostBlur = (e: Event) => { - const 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/assets/typings/frost.d.ts b/app/src/web/assets/typings/frost.d.ts deleted file mode 100644 index 8f60c9dd..00000000 --- a/app/src/web/assets/typings/frost.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -declare interface FrostJSI { - loadUrl(url: string | null): boolean - - loadVideo(url: string | null, isGif: boolean): boolean - - reloadBaseUrl(animate: boolean) - - contextMenu(url: string | null, text: string | null) - - longClick(start: boolean) - - disableSwipeRefresh(disable: boolean) - - loadLogin() - - loadImage(imageUrl: string, text: string | null) - - emit(flag: number) - - isReady() - - handleHtml(html: string | null) - - handleHeader(html: string | null) -} - -declare var Frost: FrostJSI; 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/scss/core/_base.scss b/app/src/web/scss/core/_base.scss new file mode 100644 index 00000000..472319fe --- /dev/null +++ b/app/src/web/scss/core/_base.scss @@ -0,0 +1,107 @@ +@mixin placeholder { + ::-webkit-input-placeholder { + @content; + } + + :-moz-placeholder { + @content; + } + + ::-moz-placeholder { + @content; + } + + :-ms-input-placeholder { + @content; + } +} + +@mixin keyframes($name) { + @-webkit-keyframes #{$name} { + @content; + } + + @-moz-keyframes #{$name} { + @content; + } + + //@-ms-keyframes #{$name} { + // @content; + //} + + @keyframes #{$name} { + @content; + } +} + +// Helper function to replace characters in a string +@function str-replace($string, $search, $replace: "") { + $index: str-index($string, $search); + + @return if($index, str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace), $string); +} + +// https://css-tricks.com/probably-dont-base64-svg/ +// SVG optimization thanks to https://codepen.io/jakob-e/pen/doMoML +// Function to create an optimized svg url +// Version: 1.0.6 +@function svg-url($svg) { + // + // Add missing namespace + // + @if not str-index($svg, xmlns) { + $svg: str-replace($svg, "", "%3E"); + + // + // The maybe list + // + // Keep size and compile time down + // ... only add on documented fail + // + // $chunk: str-replace($chunk, '&', '%26'); + // $chunk: str-replace($chunk, '|', '%7C'); + // $chunk: str-replace($chunk, '[', '%5B'); + // $chunk: str-replace($chunk, ']', '%5D'); + // $chunk: str-replace($chunk, '^', '%5E'); + // $chunk: str-replace($chunk, '`', '%60'); + // $chunk: str-replace($chunk, ';', '%3B'); + // $chunk: str-replace($chunk, '?', '%3F'); + // $chunk: str-replace($chunk, ':', '%3A'); + // $chunk: str-replace($chunk, '@', '%40'); + // $chunk: str-replace($chunk, '=', '%3D'); + + $encoded: #{$encoded}#{$chunk}; + $index: $index + $slice; + } + + @return url("data:image/svg+xml,#{$encoded}"); +} + +// Background svg mixin +@mixin background-svg($svg, $extra: "no-repeat") { + background: svg-url($svg) unquote($extra) !important; +} diff --git a/app/src/web/scss/core/_colors.scss b/app/src/web/scss/core/_colors.scss new file mode 100644 index 00000000..7610572c --- /dev/null +++ b/app/src/web/scss/core/_colors.scss @@ -0,0 +1,18 @@ +$bg_transparent: rgba(#f0f, 0.02) !default; + +//Keep above as first line so partials aren't compiled +//Our default colors are test colors; production files should always import the actual colors + +$text: #d7b0d7 !default; +// must be visible with accent as the background +$accent_text: #76d7c2 !default; +$link: #9266d5 !default; +$accent: #980008 !default; +$background: #451515 !default; +// background2 must be transparent +$background2: rgba(lighten($background, 35%), 0.35) !default; //Also change ratio in material_light +$bg_opaque: rgba($background, 1.0) !default; +$bg_opaque2: rgba($background2, 1.0) !default; +$card: #239645 !default; +$tint: #ff4682 !default; // must be different from $background +$divider: rgba($text, 0.3) !default; diff --git a/app/src/web/scss/core/_core_bg.scss b/app/src/web/scss/core/_core_bg.scss new file mode 100644 index 00000000..494ee0c1 --- /dev/null +++ b/app/src/web/scss/core/_core_bg.scss @@ -0,0 +1,86 @@ +#viewport { + background: $background !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: $bg_transparent !important; +} + +//card related +._31nf, ._2v9s, ._d4i, article._55wo, ._10c_, ._2jl2, ._6150, ._50mi, ._4-dw, ._4_2z, ._5m_s, ._13fn { + background: $card !important; +} + +// unread related + +.aclb { + background: $tint !important; +} + +//contains images so must have background-color +._cv_, ._2sq8 { + background-color: $bg_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: $bg_opaque !important; +} + +._403n, ._14v5 ._14v8, ._1-kc { + background: $bg_opaque2 !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: $background2 !important; +} + +.mQuestionsPollResultsBar .shaded { + background: $accent !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: $divider !important; +} + +//fab +button ._v89 ._54k8._1fl1 { + background: $accent !important; +} diff --git a/app/src/web/scss/core/_core_border.scss b/app/src/web/scss/core/_core_border.scss new file mode 100644 index 00000000..9f2bdec0 --- /dev/null +++ b/app/src/web/scss/core/_core_border.scss @@ -0,0 +1,94 @@ +//border between like and comment +._15kl::before, ._37fd .inlineComposerButton, ._1hb:before, +._5j35::after, ._2k4b, ._3to7, ._4nw8 { + border-left: 1px solid $divider !important; +} + +._4_d1, ._5cni, ._3jcq { + border-right: 1px solid $divider !important; +} + +//above see more +._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 $divider !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 $divider !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 $divider !important; + border-bottom: 1px solid $divider !important; +} + +//friend card border +._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 $divider !important; +} + +.mQuestionsPollResultsBar .shaded, ._1027._13sm { + border: 1px solid $text !important; +} + +._3gka { + border: 1px dashed $divider !important; +} + +//link card bottom border +._4o58::after, .acr, ._t21, ._2bdb, +.acw, .aclb, ._4qax, ._5h8f { + border-color: $divider !important; +} + +// like, comment, share divider +._15ks ._15kl::before { + border-left: 1px solid transparent !important; +} + +._56bf, .touch .btn { + border-radius: 0 !important; + border: 0 !important; +} + +//page side tab layout +._2cis { + border-left: 10px solid $bg_transparent !important; + border-right: 10px solid $bg_transparent !important; +} + +._2cir.selected, ._42rv, ._5zma, ._2x2s { + border-bottom: 3px solid $text !important; +} + +._1ss6 { + border-left: 2px solid $text !important; +} + +._484w.selected > ._6zf, ._5kqs::after, ._3lvo ._5xum._5xuk, ._x0b { + border-bottom: 1px solid $text !important; +} diff --git a/app/src/web/scss/core/_core_messenger.scss b/app/src/web/scss/core/_core_messenger.scss new file mode 100644 index 00000000..608fc23d --- /dev/null +++ b/app/src/web/scss/core/_core_messenger.scss @@ -0,0 +1,20 @@ +// Not all messenger related components are here; only the main ones. +// Borders for instance are merged into core_border + +// Other person's message bubble +._34ee { + background: $background2 !important; + color: $text !important; + +} + +// Your message bubble; order matters +._34em ._34ee { + background: $accent !important; + color: $accent_text !important; +} + +// Sticker page +._5as0, ._5cni, ._5as2 { + background: $bg_opaque !important; +} \ No newline at end of file diff --git a/app/src/web/scss/core/_core_text.scss b/app/src/web/scss/core/_core_text.scss new file mode 100644 index 00000000..63622610 --- /dev/null +++ b/app/src/web/scss/core/_core_text.scss @@ -0,0 +1,40 @@ +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: $text !important; +} + +strong > a, ._15ks ._2q8z._2q8z, ._1e3e, .blueName, ._5kqs ._55sr { + color: $accent !important; +} + +._42nf ._42ng { + color: transparent !important; +} + +// most links do not have a special color. We will highlight those in posts and messages +p > a, .msg span > a { + color: $link !important; +} diff --git a/app/src/web/scss/core/_main.scss b/app/src/web/scss/core/_main.scss new file mode 100644 index 00000000..3e972f93 --- /dev/null +++ b/app/src/web/scss/core/_main.scss @@ -0,0 +1,6 @@ +@import "core"; +@import "svg"; + +//this file is used as the base for all themes +//given that svgs take a lot of characters, we won't compile them when testing +//therefore we use the core scss diff --git a/app/src/web/scss/core/_svg.scss b/app/src/web/scss/core/_svg.scss new file mode 100644 index 00000000..8c714438 --- /dev/null +++ b/app/src/web/scss/core/_svg.scss @@ -0,0 +1,74 @@ +// icons courtesy of https://material.io/icons/ + +$camera: ''; + +// status upload image +._50uu { + @include background-svg($camera); +} + +$video: ''; + +// status upload video +._50uw { + @include background-svg($video); +} + +$like: ''; +$like_selected: ''; + +// 2018/12/29 +// Previously ._15km ._15ko::before and ._15km ._15ko._77la::before; however, reaction changes no longer affect this element +// The robust measure seems to be the parent of a[data-sigil~="like-reaction-flyout"] along with [data-sigil~="like"] for an unliked post +// and [data-sigil~="unlike"] for a liked post +._15km ._15ko::before { + @include background-svg($like); + background-position: center !important; +} + +._15km ._15ko._77la::before { + @include background-svg($like_selected); + background-position: center !important; +} + +$comment: ''; + +._15km ._15kq::before { + @include background-svg($comment); + background-position: center !important; +} + +$share: ''; + +._15km ._15kr::before { + @include background-svg($share); + background-position: center !important; +} + +$more_horiz: ''; + +//$menus: ".sp_89zNula0Qh5", +//".sp_MP2OtCXORz9", +//".sp_NIWBacTn8LF", +//// 2018/12/31 +//".sp_9ZFVhnFyWsw", +//// 2019/01/03 +//".sp_SJIJjSlGEIO"; +// +//$menu_collector: (); +// +//@each $menu in $menus { +// $menu_collector: append($menu_collector, unquote('#{$menu}'), 'comma'); +// $menu_collector: append($menu_collector, unquote('#{$menu}_2x'), 'comma'); +// $menu_collector: append($menu_collector, unquote('#{$menu}_3x'), 'comma'); +//} +// +//#{$menu_collector} { +// @include background-svg($more_horiz); +// background-position: center !important; +//} + +.story_body_container i.img[data-sigil*="story-popup-context"] { + @include background-svg($more_horiz); + background-position: center !important; +} \ No newline at end of file diff --git a/app/src/web/scss/core/core.scss b/app/src/web/scss/core/core.scss new file mode 100644 index 00000000..38086529 --- /dev/null +++ b/app/src/web/scss/core/core.scss @@ -0,0 +1,54 @@ +@import "colors"; +@import "base"; +@import "core_text"; +@import "core_bg"; +@import "core_border"; +@import "core_messenger"; + +//GLOBAL overrides; use with caution +*, *::after, *::before { + text-shadow: none !important; + box-shadow: none !important; +} + +// .touch .btnS, button, ._94v, ._590n { +// box-shadow: none !important; +// } + +[data-sigil=m_login_upsell], +[data-sigil="m-loading-indicator-animate m-loading-indicator-root"] { + display: none !important; +} + +@include placeholder { + color: $text !important; +} + +.excessItem { + outline: $divider !important; +} + +._3m1m { + background: linear-gradient(transparent, $bg_opaque) !important; +} + +//new comment +@include keyframes(highlightFade) { + 0%, 50% { + background: $background2; + } + + 100% { + background: $bg_transparent; + } +} + +@include keyframes(chatHighlightAnimation) { + 0%, 100% { + background: $bg_transparent; + } + + 50% { + background: $background2; + } +} diff --git a/app/src/web/scss/themes/.gitignore b/app/src/web/scss/themes/.gitignore new file mode 100644 index 00000000..4c46adff --- /dev/null +++ b/app/src/web/scss/themes/.gitignore @@ -0,0 +1 @@ +test.scss diff --git a/app/src/web/scss/themes/custom.scss b/app/src/web/scss/themes/custom.scss new file mode 100644 index 00000000..50c029fb --- /dev/null +++ b/app/src/web/scss/themes/custom.scss @@ -0,0 +1,14 @@ +$bg_transparent: unquote('$BT$'); +$text: unquote('$T$'); +$link: unquote('$TT$'); +$accent: unquote('$A$'); +$accent_text: unquote('$AT$'); +$background: unquote('$B$'); +$background2: unquote('$BBT$'); +$bg_opaque: unquote('$O$'); +$bg_opaque2: unquote('$OO$'); +$divider: unquote('$D$'); +$card: unquote('$C$'); +$tint: unquote('$TI$'); + +@import "../core/main"; diff --git a/app/src/web/scss/themes/material_amoled.scss b/app/src/web/scss/themes/material_amoled.scss new file mode 100644 index 00000000..19190126 --- /dev/null +++ b/app/src/web/scss/themes/material_amoled.scss @@ -0,0 +1,11 @@ +$text: #fff; +$accent_text: #fff; +$link: #5d86dd; +$accent: #5d86dd; +$background: #000; +$background2: rgba($background, 0.35); +$bg_transparent: $background; +$card: $background2; +$tint: rgba(#fff, 0.2); + +@import "../core/main"; diff --git a/app/src/web/scss/themes/material_dark.scss b/app/src/web/scss/themes/material_dark.scss new file mode 100644 index 00000000..18b8b461 --- /dev/null +++ b/app/src/web/scss/themes/material_dark.scss @@ -0,0 +1,10 @@ +$text: #fff; +$accent_text: #fff; +$link: #5d86dd; +$accent: #5d86dd; +$background: #303030; +$bg_transparent: $background; +$card: #353535; +$tint: rgba(#fff, 0.2); + +@import "../core/main"; diff --git a/app/src/web/scss/themes/material_glass.scss b/app/src/web/scss/themes/material_glass.scss new file mode 100644 index 00000000..0c61a38c --- /dev/null +++ b/app/src/web/scss/themes/material_glass.scss @@ -0,0 +1,10 @@ +$text: #fff; +$accent_text: #fff; +$link: #5d86dd; +$accent: #5d86dd; +$background: rgba(#000, 0.1); +$bg_transparent: transparent; +$card: rgba(#000, 0.25); +$tint: rgba(#fff, 0.15); + +@import "../core/main"; diff --git a/app/src/web/scss/themes/material_light.scss b/app/src/web/scss/themes/material_light.scss new file mode 100644 index 00000000..7ec58463 --- /dev/null +++ b/app/src/web/scss/themes/material_light.scss @@ -0,0 +1,15 @@ +$text: #000; +$accent_text: #fff; +$link: #3b5998; +$accent: #3b5998; +$background: #fafafa; +// this is actually the inverse of material light (bg should be gray, cards should be white), +// but it looks better than the alternative +$background2: rgba(darken($background, 8%), 0.35); + +$bg_transparent: $background; + +$card: #fff; +$tint: #ddd; + +@import "../core/main"; \ No newline at end of file diff --git a/app/src/web/ts/click_a.ts b/app/src/web/ts/click_a.ts new file mode 100644 index 00000000..5023610e --- /dev/null +++ b/app/src/web/ts/click_a.ts @@ -0,0 +1,57 @@ +(function () { + let prevented = false; + + const _frostAClick = (e: Event) => { + // check for valid target + const target = e.target || e.currentTarget || e.srcElement; + if (!(target instanceof Element)) { + console.log("No element found"); + return + } + let element: Element = target; + // Notifications are two layers under + for (let i = 0; i < 2; i++) { + if (element.tagName !== 'A') { + element = element.parentElement; + } + } + if (element.tagName === 'A') { + if (!prevented) { + const url = element.getAttribute('href'); + if (!url || url === '#') { + return + } + console.log(`Click intercept ${url}`); + // If Frost is injected, check if loading the url through an overlay works + if (Frost.loadUrl(url)) { + e.stopPropagation(); + e.preventDefault(); + } + } else { + console.log("Click intercept prevented") + } + } + }; + + /* + * On top of the click event, we must stop it for long presses + * Since that will conflict with the context menu + * Note that we only override it on conditions where the context menu + * Will occur + */ + const _frostPreventClick = () => { + console.log("Click _frostPrevented"); + prevented = true; + }; + + document.addEventListener('click', _frostAClick, true); + let clickTimeout: number | undefined = undefined; + document.addEventListener('touchstart', () => { + clickTimeout = setTimeout(_frostPreventClick, 400); + }, true); + document.addEventListener('touchend', () => { + prevented = false; + clearTimeout(clickTimeout) + }, true); +}).call(undefined); + diff --git a/app/src/web/ts/click_debugger.ts b/app/src/web/ts/click_debugger.ts new file mode 100644 index 00000000..088271fa --- /dev/null +++ b/app/src/web/ts/click_debugger.ts @@ -0,0 +1,15 @@ +// For desktop only + +(function () { + const _frostAContext = (e: Event) => { + // Commonality; check for valid target + const 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/ts/context_a.ts b/app/src/web/ts/context_a.ts new file mode 100644 index 00000000..5eec7611 --- /dev/null +++ b/app/src/web/ts/context_a.ts @@ -0,0 +1,125 @@ +/** + * Context menu for links + * Largely mimics click_a.js + */ + +(function () { + let longClick = false; + + /** + * Given event and target, return true if handled and false otherwise. + */ + type EventHandler = (e: Event, target: HTMLElement) => Boolean + + const _frostCopyComment: EventHandler = (e, target) => { + if (!target.hasAttribute('data-commentid')) { + return false; + } + const text = target.innerText; + console.log(`Copy comment ${text}`); + Frost.contextMenu(null, text); + return true; + }; + + /** + * Posts should click a tag, with two parents up being div.story_body_container + */ + const _frostCopyPost: EventHandler = (e, target) => { + if (target.tagName !== 'A') { + return false; + } + const parent1 = target.parentElement; + if (!parent1 || parent1.tagName !== 'DIV') { + return false; + } + const parent2 = parent1.parentElement; + if (!parent2 || !parent2.classList.contains('story_body_container')) { + return false; + } + const url = target.getAttribute('href'); + const text = parent1.innerText; + console.log(`Copy post ${url} ${text}`); + Frost.contextMenu(url, text); + return true; + }; + + const _getImageStyleUrl = (el: Element): string | null => { + const img = el.querySelector("[style*=\"background-image: url(\"]"); + if (!img) { + return null + } + return (window.getComputedStyle(img, null).backgroundImage).trim().slice(4, -1); + }; + + /** + * Opens image activity for posts with just one image + */ + const _frostImage: EventHandler = (e, target) => { + let element: Element = target; + // Notifications are two layers under + for (let i = 0; i < 2; i++) { + if (element.tagName !== 'A') { + element = element.parentElement; + } else { + break + } + } + if (element.tagName !== 'A') { + return false; + } + const url = element.getAttribute('href'); + if (!url || url === '#') { + return false; + } + const text = (element.parentElement).innerText; + // Check if image item exists, first in children and then in parent + const imageUrl = _getImageStyleUrl(element) || _getImageStyleUrl(element.parentElement); + if (imageUrl) { + console.log(`Context image: ${imageUrl}`); + Frost.loadImage(imageUrl, text); + return true; + } + // Check if true img exists + const img = element.querySelector("img[src*=scontent]"); + if (img instanceof HTMLMediaElement) { + const 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; + }; + + const handlers = [_frostImage, _frostCopyComment, _frostCopyPost]; + + const _frostAContext = (e: Event) => { + Frost.longClick(true); + longClick = true; + + /* + * Commonality; check for valid target + */ + const target = e.target || e.currentTarget || e.srcElement; + if (!(target instanceof HTMLElement)) { + console.log("No element found"); + return + } + for (const h of handlers) { + if (h(e, target)) { + e.stopPropagation(); + e.preventDefault(); + return + } + } + }; + + document.addEventListener('contextmenu', _frostAContext, true); + document.addEventListener('touchend', () => { + if (longClick) { + Frost.longClick(false); + longClick = false + } + }, true); +}).call(undefined); diff --git a/app/src/web/ts/document_watcher.ts b/app/src/web/ts/document_watcher.ts new file mode 100644 index 00000000..e671149c --- /dev/null +++ b/app/src/web/ts/document_watcher.ts @@ -0,0 +1,27 @@ +// Emit key once half the viewport is covered +(function () { + const isReady = () => { + return document.body.scrollHeight > innerHeight + 100 + }; + + if (isReady()) { + console.log('Already ready'); + Frost.isReady(); + return + } + + console.log('Injected document watcher'); + + const observer = new MutationObserver(() => { + 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/ts/header_badges.ts b/app/src/web/ts/header_badges.ts new file mode 100644 index 00000000..473749f2 --- /dev/null +++ b/app/src/web/ts/header_badges.ts @@ -0,0 +1,7 @@ +// Fetches the header contents if it exists +(function() { + const header = document.getElementById('mJewelNav'); + if (header) { + Frost.handleHeader(header.outerHTML); + } +}).call(undefined); diff --git a/app/src/web/ts/media.ts b/app/src/web/ts/media.ts new file mode 100644 index 00000000..5b9b1a54 --- /dev/null +++ b/app/src/web/ts/media.ts @@ -0,0 +1,47 @@ +// Handles media events +(function () { + const _frostMediaClick = (e: Event) => { + const target = e.target || e.srcElement; + if (!(target instanceof HTMLElement)) { + return + } + let element: HTMLElement = target; + const dataset = element.dataset; + if (!dataset || !dataset.sigil || dataset.sigil.toLowerCase().indexOf('inlinevideo') == -1) { + return + } + let i = 0; + while (!element.hasAttribute('data-store')) { + if (++i > 2) { + return + } + element = element.parentNode; + } + const store = element.dataset.store; + if (!store) { + return + } + + let dataStore; + + try { + dataStore = JSON.parse(store) + } catch (e) { + return + } + + const url = dataStore.src; + + // !startsWith; see https://stackoverflow.com/a/36876507/4407321 + 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/ts/menu.ts b/app/src/web/ts/menu.ts new file mode 100644 index 00000000..6f9dbf16 --- /dev/null +++ b/app/src/web/ts/menu.ts @@ -0,0 +1,59 @@ +// Click menu and move contents to main view +(function () { + const viewport = document.querySelector("#viewport"); + const root = document.querySelector("#root"); + const bookmarkJewel = document.querySelector("#bookmarks_jewel"); + if (!viewport || !root || !bookmarkJewel) { + console.log('Menu.js: main elements not found'); + Frost.emit(0); + return + } + const menuA = bookmarkJewel.querySelector("a"); + if (!menuA) { + console.log('Menu.js: menu links not found'); + Frost.emit(0); + return + } + const jewel = document.querySelector('#mJewelNav'); + if (!jewel) { + console.log('Menu.js: jewel is null'); + return + } + + const y = new MutationObserver(() => { + viewport.removeAttribute('style'); + root.removeAttribute('style'); + }); + + y.observe(viewport, { + attributes: true + }); + y.observe(root, { + attributes: true + }); + + const x = new MutationObserver(() => { + const menu = document.querySelector('.mSideMenu'); + if (menu) { + x.disconnect(); + console.log("Found side menu"); + // Transfer elements + while (root.firstChild) { + root.removeChild(root.firstChild); + } + while (menu.childNodes.length) { + viewport.appendChild(menu.childNodes[0]); + } + Frost.emit(0); + setTimeout(() => { + y.disconnect(); + console.log('Unhook styler'); + }, 500); + } + }); + x.observe(jewel, { + childList: true, + subtree: true + }); + menuA.click(); +}).call(undefined); diff --git a/app/src/web/ts/notif_msg.ts b/app/src/web/ts/notif_msg.ts new file mode 100644 index 00000000..b7ce7a19 --- /dev/null +++ b/app/src/web/ts/notif_msg.ts @@ -0,0 +1,25 @@ +// Binds callback to an invisible webview to take in the search events +(function () { + let finished = false; + const x = new MutationObserver(() => { + const _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(() => { + if (!finished) { + finished = true; + console.log('Message thread timeout cancellation'); + Frost.handleHtml("") + } + }, 20000); +}).call(undefined); diff --git a/app/src/web/ts/textarea_listener.ts b/app/src/web/ts/textarea_listener.ts new file mode 100644 index 00000000..062f5bf6 --- /dev/null +++ b/app/src/web/ts/textarea_listener.ts @@ -0,0 +1,31 @@ +/* + * focus listener for textareas + * since swipe to refresh is quite sensitive, we will disable it + * when we detect a user typing + * note that this extends passed having a keyboard opened, + * as a user may still be reviewing his/her post + * swiping should automatically be reset on refresh + */ +(function () { + const _frostFocus = (e: Event) => { + const element = e.target || e.srcElement; + if (!(element instanceof Element)) { + return + } + console.log(`FrostJSI focus, ${element.tagName}`); + if (element.tagName === 'TEXTAREA') { + Frost.disableSwipeRefresh(true); + } + }; + + const _frostBlur = (e: Event) => { + const 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/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/typings/frost.d.ts b/app/src/web/typings/frost.d.ts new file mode 100644 index 00000000..8f60c9dd --- /dev/null +++ b/app/src/web/typings/frost.d.ts @@ -0,0 +1,27 @@ +declare interface FrostJSI { + loadUrl(url: string | null): boolean + + loadVideo(url: string | null, isGif: boolean): boolean + + reloadBaseUrl(animate: boolean) + + contextMenu(url: string | null, text: string | null) + + longClick(start: boolean) + + disableSwipeRefresh(disable: boolean) + + loadLogin() + + loadImage(imageUrl: string, text: string | null) + + emit(flag: number) + + isReady() + + handleHtml(html: string | null) + + handleHeader(html: string | null) +} + +declare var Frost: FrostJSI; 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/generate-apk-release.sh b/generate-apk-release.sh old mode 100644 new mode 100755 index 00091f4b..d3a44577 --- 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 4c0ace29..86e6040a 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 -- cgit v1.2.3 From 43a7e4b8e0f0a97f21514c5910b7e89d8b0bd814 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 1 May 2019 11:54:31 -0700 Subject: Delete duplicate translations --- app/src/main/res/values-es/strings_pref_feed.xml | 15 --------------- app/src/main/res/values-it/strings_pref_feed.xml | 15 --------------- 2 files changed, 30 deletions(-) delete mode 100644 app/src/main/res/values-es/strings_pref_feed.xml delete mode 100644 app/src/main/res/values-it/strings_pref_feed.xml 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 @@ - - - Orden de las Publicaciones - Define el orden en que aparecen las publicaciones - Modo \"Más Recientes\" agresivo - 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. - Escritor de Estado - Mostrar escritor de estado en el feed - Sugerencias de Amigos - Mostrar \"Gente que quizá conozcas\" en el feed - Grupos sugeridos - Mostrar \"grupos sugeridos\" en el feed - Publicidad de Facebook - Mostrar Publicidad Nativa de Facebook - 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 @@ - - - Ordine della Sezione Notizie - Definisce l\'ordine in cui sono mostrati i post - Recenti Aggressivi - Filtra ulteriori post vecchi dalla sezione originale più recenti di Facebook. Disabilita se il tuo feed è vuoto. - Compositore di Stato - Mostra la casella per comporre uno stato nelle Notizie - Amici Suggeriti - Mostra \"Persone Che Potresti Conoscere\" nel feed - Gruppi Suggeriti - Mostra \"Gruppi Suggeriti\" nel feed - Pubblicità Facebook - Mostra le pubblicità native di Facebook - -- cgit v1.2.3 From dda14a0ac7ba531e8e2d69df2c769a7d6020a782 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 1 May 2019 12:01:49 -0700 Subject: Update Crowdin configuration file --- crowdin.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 crowdin.yml 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% -- cgit v1.2.3 From 2f476dae5a128ea5b3939102391ed42f28dd5ec4 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 1 May 2019 12:03:13 -0700 Subject: Split yml paths --- crowdin.yaml | 16 ---------------- crowdin.yml | 4 +++- 2 files changed, 3 insertions(+), 17 deletions(-) delete mode 100644 crowdin.yaml 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 index b0b57281..9a4922bf 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,5 +1,7 @@ files: - - source: /app/src/main/res/values/strings*.xml + - source: + - /app/src/main/res/values/strings.xml + - /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% -- cgit v1.2.3 From 7da6d6459f0ea0d4f8c2b18fec2c2ae5049ab15b Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 1 May 2019 12:04:15 -0700 Subject: Revert --- crowdin.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crowdin.yml b/crowdin.yml index 9a4922bf..b0b57281 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,7 +1,5 @@ files: - - source: - - /app/src/main/res/values/strings.xml - - /app/src/main/res/values/strings_*.xml + - 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% -- cgit v1.2.3 From 58850fca392e458383d2546eeb83106bd54121e2 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 1 May 2019 13:17:59 -0700 Subject: Fix translation for yesterday --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0c2e625c..db9b30d5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -63,7 +63,7 @@ No new notifications found Today - Today + Yesterday 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 @@ Valgmuligheder Hold nede og træk for at flytte de øverste ikoner. Ingen nye notifikationer + 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 @@ Geburtstage Chat Fotos + Marktplatz Notizen An diesem Tag Alles wird vorbereitet… @@ -45,4 +46,12 @@ Optionen Durch langes Drücken und Ziehen können Sie die oberen Symbole neu anordnen. Keine neue Benachrichtigungen gefunden + 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 @@ Zeige \"Leute die du vielleicht kennst\" im Feed Empfohlene Gruppen Zeige \"Empfohlene Gruppen\" im Feed + Story\'s anzeigen + Story\'s in den Feed anzeigen Facebook Werbung Zeige native Facebook Werbung 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 @@ Cumpleaños Chat Fotos + Marketplace Notas En este día Preparando todo… @@ -45,4 +46,14 @@ Opciones Mantén pulsado y arrastra para reorganizar los iconos superiores. No se han encontrado Notificaciones + Hoy + Ayer + 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 @@ 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. Activar PIP Activar función de video en miniatura + Configuración de jugadas automáticas + Abra configuración de juego de auto de Facebook. Tenga en cuenta que debe estar desactivada para que PIP trabajar. Confirmar salida Muestra un diálogo de confirmación antes de salir de la app Analytics 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 @@ Mostrar \"Gente que quizá conozcas\" en el feed Grupos sugeridos Mostrar \"grupos sugeridos\" en el feed + Historias destacadas + Mostrar historias en el feed Anuncios de Facebook Mostrar anuncios nativos de Facebook 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 @@ Anniversaires Conversations Photos + Marketplace Notes Aujourd\'hui Tout se prépare… @@ -45,4 +46,15 @@ Options Appuyez longuement et faites glisser pour réorganiser les icônes du haut. Pas de nouvelles notifications trouvées + Aujourd\'hui + Hier + + %1s à %2s 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 @@ 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. Activer le PIP Activer les vidéos Picture In Picture + Paramètres de lecture automatique + Ouvrir les paramètres de lecture automatique de Facebook. Notez qu\'il doit être désactivé pour que PIP fonctionne. Confirmation de la sortie Afficher la boîte de dialogue de confirmation avant de quitter l’application Analytics 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 @@ Ordre du fil d\'actualité Définit l’ordre dans lequel les messages sont affichés Récents agressifs - 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. + É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. Compositeur de statut Montrer le compositeur de statut dans le fil d\'actualité Amis suggérés Afficher les «Personnes que vous pouvez connaître» dans le fil d\'actualité Groupes Suggérés Afficher les «Groupes Suggérés» dans le fil d\'actualité + Montrer les Top Stories + Montrer les stories dans le fil d\'actualité Publicités Facebook Afficher les publicités Facebook 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 @@ Opcións Toque longo e arrastra para reorganizar as iconas superiores. Non se atopou ningunha nova notificación + 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 @@ Születésnapok Chat Fényképek + Piactér Jegyzetek Ezen a napon Előkészítés… @@ -45,4 +46,12 @@ Beállítások Tartsd nyomva és húzd a felső ikonokat az átrendezéshez. Nem találhatók új értesítések + 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 @@ \"Emberek, akiket ismerhetsz\" megjelenítése a hírcsatornában Javasolt csoportok \"Javasolt csoportok\" megjelenítése a hírcsatornában + Történetek megjelenítése + Történetek megjelenítése a hírfolyamban Facebook hirdetések Natív Facebook-hirdetések megjelenítése 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 @@ Pratinjau Pilihan Tekan lama dan tarik untuk mengatur ulang ikon atas. + 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 @@ Opzioni Per riordinare un\'icona tienila premuta e trascinala. Nessuna nuova notifica trovata + 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 @@ 파일 선택기를 찾을 수 없습니다. 상단 바 하단 바 + 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 @@ Opties Klik en houd vast om de iconen in de gewenste volgorde te slepen. Geen nieuwe notificaties + 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 @@ Forhåndsvisning Alternativer Langt trykk og dra for å endre topp ikonene. + 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 @@ Opcje Długie naciśnięcie i przeciągnięcie, aby zmienić kolejność ikon. Brak nowych powiadomień + 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 @@ Aniversários Amigos online Fotos + Marketplace Notas Neste Dia Preparando tudo… @@ -46,4 +47,15 @@ Opções Mantenha pressionado e arraste para reorganizar os ícones superiores. Nenhuma nova notificação encontrada + Hoje + Ontem + + %1s às %2s 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 @@ 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á. Habilitar o PIP Habilita o Picture in Picture (janelas flutuantes de vídeos) + Configurações de reprodução automática + Abra as configurações de reprodução automática do Facebook. Observe que ele deve ser desativado para que o PIP funcione. Confirmação de Saída Mostrar caixa de diálogo de confirmação antes de sair do aplicativo Telemetria 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 @@ Mostra \"Pessoas Que Talvez Você Conheça\" no Feed Grupos Sugeridos Mostra \"Grupos Sugeridos\" no Feed + Mostrar Histórias + Mostrar histórias no feed Anúncios do Facebook Mostrar anúncios nativos do Facebook 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 @@ Opções Toque longo e arraste para dispor os ícones superiores. Sem notificações novas + 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 @@ Opțiuni Apasă lung și trage să rearanjezi. Nu s-au găsit notificări + 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 @@ Дни рождения Написать Фотографии + Marketplace Заметки В этот день Почти готово… @@ -45,4 +46,14 @@ Опции Долго нажмите и перетащите чтобы переставить иконки Новые уведомления отсутствуют + Сегодня + Вчера + 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 @@ Смотреть «Люди которых вы можете знать» в канале Предлагаемые группы Смотреть «Предложения групп» в канале + Показывать Истории + Показывать Истории в ленте - Реклама в Facebook Показать родной Facebook объявления 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 @@ Опције Задржите и превуците да би прерасподелили горње иконице. Нема нових обавештења + 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 @@ Inställningar Tryck och håll kvar för att arrangera om topp-ikonerna. Inga nya notifikationer hittades + 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 @@ แสดงตัวอย่าง ตัวเลือก กดค้างและลากเพื่อจัดเรียงไอคอนด้านบน + 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 @@ Pribyu Ang mga opsyon Pindutin ng matagal at hilahin para mabago ang ayos ng pangunahing imahe. + 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 @@ Doğum Günleri Sohbet Fotoğraflar + Pazar yeri Notlar Bu günde Her şey hazır alınıyor… @@ -45,4 +46,14 @@ Seçenekler Üstteki simgeleri yeniden düzenlemek için uzun basın ve sonra sürükleyin. Yeni bildirim bulunmadı + Bugün + Dün + 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 @@ Birileti dizisi yüklerken, sayfayı olduğu gibi yüklemek yerine, sayfanın altına kaydırma yapın. PIP\'i etkinleştir PIP (Picture in Picture) videolarını etkinleştir + Otomatik oynatma ayarları Çıkış Onayı Uygulamadan çıkmadanönce onay iletişim kutusunu göster Analiz 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 @@ Özet akışında \"Tanıdığınız İnsanları\" gösterin Önerilen gruplar Özet akışında \"önerilen grup\" ları göster + Hikayeleri göster + Akışda ki hikayeleri göster Facebook reklamları Yerli Facebook reklamlarını göster 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 @@ Дні народження Чат Фотографії + Магазин Замітки Цього дня Отримання всього готове… @@ -45,4 +46,12 @@ Опції Довге натискання та перетягніть, щоб переставити верхній значок. Нових повідомлень не знайдено + 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 @@ Показати \"Люди, яких ви можете знати\" у новинній стрічці Пропоновані групи Показати \"Пропоновані групи\" у новинній стрічці + Показати Історії + Показувати історії у стрічці Реклама у Facebook Показувати вбудовану рекламу Facebook 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 @@ Tuỳ chọn Bấm giữ và kéo để sắp xếp biểu tượng trên cùng. Không có thông báo mới + 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 @@ 未找到文件选择程序 顶栏 底栏 + 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 @@ 選項 長按及拖曳頂部圖標可重新排列位置 沒有新通知。 + 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" "" "./" | tr '\n' '\0' | xargs -0 -n1 rm + grep -Lir "" --include="strings*.xml" "app/src/main/res" | tr '\n' '\0' | xargs -0 -n1 rm # Delete empty directories find . -type d -empty -delete fi -- cgit v1.2.3 From da93672c2ed6b54e0e7119a6b55715185112df3e Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 1 May 2019 14:04:56 -0700 Subject: Prompt widget update post intro --- app/src/main/kotlin/com/pitchedapps/frost/activities/IntroActivity.kt | 2 ++ 1 file changed, 2 insertions(+) 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(cookies(), false) super.finish() } -- cgit v1.2.3