Forráskód Böngészése

added basic tags feature

sirekanian 3 éve
szülő
commit
7c3c7630be
35 módosított fájl, 1338 hozzáadás és 918 törlés
  1. 90 0
      app/schemas/IndexEntity.csv
  2. 1 1
      app/schemas/MetaEntity.csv
  3. 16 0
      app/schemas/TagEntity.csv
  4. 866 860
      app/schemas/WarmongerEntity.csv
  5. 67 3
      app/schemas/com.sirekanian.acf.data.local.Database/1.json
  6. 6 2
      app/schemas/init.sql
  7. BIN
      app/src/main/assets/warmongers.db
  8. 1 1
      app/src/main/java/com/sirekanian/acf/App.kt
  9. 19 5
      app/src/main/java/com/sirekanian/acf/D.kt
  10. 11 2
      app/src/main/java/com/sirekanian/acf/MainActivity.kt
  11. 7 5
      app/src/main/java/com/sirekanian/acf/MainPresenter.kt
  12. 10 0
      app/src/main/java/com/sirekanian/acf/MainState.kt
  13. 3 0
      app/src/main/java/com/sirekanian/acf/TagModel.kt
  14. 14 6
      app/src/main/java/com/sirekanian/acf/data/Repository.kt
  15. 29 0
      app/src/main/java/com/sirekanian/acf/data/Tag.kt
  16. 1 0
      app/src/main/java/com/sirekanian/acf/data/Warmonger.kt
  17. 10 1
      app/src/main/java/com/sirekanian/acf/data/local/Database.kt
  18. 11 0
      app/src/main/java/com/sirekanian/acf/data/local/IndexEntity.kt
  19. 12 0
      app/src/main/java/com/sirekanian/acf/data/local/TagDao.kt
  20. 12 0
      app/src/main/java/com/sirekanian/acf/data/local/TagEntity.kt
  21. 1 0
      app/src/main/java/com/sirekanian/acf/data/local/WarmongerEntity.kt
  22. 2 2
      app/src/main/java/com/sirekanian/acf/data/remote/WarmongerDto.kt
  23. 12 3
      app/src/main/java/com/sirekanian/acf/ext/AnimatedVisibility.kt
  24. 8 0
      app/src/main/java/com/sirekanian/acf/ext/Color.kt
  25. 1 1
      app/src/main/java/com/sirekanian/acf/ui/MainContent.kt
  26. 1 2
      app/src/main/java/com/sirekanian/acf/ui/MainFab.kt
  27. 68 0
      app/src/main/java/com/sirekanian/acf/ui/MainTags.kt
  28. 4 4
      app/src/main/java/com/sirekanian/acf/ui/MainToolbar.kt
  29. 2 2
      app/src/main/java/com/sirekanian/acf/ui/WarmongerCard.kt
  30. 19 0
      app/src/main/java/com/sirekanian/acf/ui/icons/IconDone.kt
  31. 2 3
      app/src/main/java/com/sirekanian/acf/ui/theme/Shape.kt
  32. 0 11
      app/src/main/java/com/sirekanian/acf/ui/theme/Type.kt
  33. 18 0
      app/src/main/java/com/sirekanian/acf/ui/theme/Type3.kt
  34. 2 2
      build.gradle
  35. 12 2
      update.sh

+ 90 - 0
app/schemas/IndexEntity.csv

@@ -0,0 +1,90 @@
+"11","2 3 4 5 6 7 8 10 12 13 15"
+"15","2 4 7 8 10 11 13"
+"10","2 3 4 5 6 7 8 11 13 15"
+"4","1 2 3 5 6 7 8 9 10 11 12 13 14 15 16"
+"7","2 3 4 8 10 11 13 15"
+"8","2 3 4 5 6 7 10 11 12 15"
+"6","2 3 4 5 8 10 11 12"
+"2","1 4 6 7 8 10 11 15"
+"5","3 4 6 8 10 11 12 13"
+"3","1 4 5 6 7 8 10 11 12 13"
+"1","2 3 4 9 13"
+"1 4","9"
+"1 9","4 13"
+"4 9","1"
+"9","1 4 13"
+"4 11","2 5 7 10 13 15"
+"7 11","4 10 13"
+"4 7","10 11 13"
+"4 13","5 7 10 11 15"
+"4 15","11 13"
+"13","1 3 4 5 7 9 10 11 15"
+"13 15","4"
+"8 11","5 6 10 12 15"
+"8 12","3 4 5 6 11"
+"11 12","5 8"
+"12","3 4 5 6 8 11"
+"5 8","11 12"
+"5 11","3 4 8 10 12 13"
+"7 13","3 4 10 11"
+"14","4"
+"8 10","6 11"
+"6 8","10 11 12"
+"6 10","8"
+"11 13","4 5 7 10"
+"4 11 13","5 7 10"
+"5 11 13","4"
+"4 5 13","11"
+"5 13","4 11"
+"4 5 11","13"
+"4 5","3 11 13"
+"3 8","4 12"
+"4 8","3 12"
+"3 4","5 8 10"
+"3 5","4 6 10 11"
+"3 6","5 12"
+"5 6","3"
+"4 10","2 3 7 11 13"
+"7 10","3 4 11 13"
+"2 11","4"
+"2 4","10 11"
+"10 11","3 4 5 7 8 13 15"
+"5 10","3 11"
+"10 13","4 7 11"
+"5 12","8 11"
+"6 12","3 8"
+"3 12","6 8"
+"1 13","9"
+"9 13","1"
+"2 10","4"
+"2 15","7"
+"2 7","15"
+"7 15","2"
+"11 15","4 8 10"
+"16","4"
+"4 12","8"
+"4 7 11","10 13"
+"7 11 13","4 10"
+"4 7 13","10 11"
+"10 15","11"
+"3 10","4 5 7 11"
+"3 7","10 13"
+"6 11","8"
+"4 10 13","7 11"
+"4 7 10","11 13"
+"7 10 13","4 11"
+"4 7 10 11","13"
+"4 7 10 13","11"
+"4 7 11 13","10"
+"4 10 11","7 13"
+"4 10 11 13","7"
+"7 10 11","4 13"
+"7 10 11 13","4"
+"10 11 13","4 7"
+"8 15","11"
+"3 10 11","5"
+"5 10 11","3"
+"3 11","5 10"
+"3 5 11","10"
+"3 5 10","11"
+"3 13","7"

+ 1 - 1
app/schemas/MetaEntity.csv

@@ -1 +1 @@
-"date","2022-06-04"
+"date","2022-06-18"

+ 16 - 0
app/schemas/TagEntity.csv

@@ -0,0 +1,16 @@
+1,"Organizers of Repressions","Организаторы репрессий"
+2,"Local Officials","Региональные чиновники"
+3,"Propagandists","Пропагандисты"
+4,"Corrupt Officials","Коррупционеры"
+5,"Parliament and Senate","Депутаты и сенаторы"
+6,"Warmongers","Разжигатели войны"
+7,"Federal Officials","Федеральные чиновники"
+8,"Putin’s Supporters","Сторонники Путина"
+9,"Siloviki","Cиловики"
+10,"Top Managers","Руководители госкомпаний"
+11,"United Russia","Единая Россия"
+12,"Influencers","Инфлюенсеры"
+13,"War Arrangers","Организаторы войны"
+14,"Election Fraud","Фальсификаторы выборов"
+15,"Region Heads","Главы регионов"
+16,"Bankers","Банкиры"

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 866 - 860
app/schemas/WarmongerEntity.csv


+ 67 - 3
app/schemas/com.sirekanian.acf.data.local.Database/1.json

@@ -2,7 +2,7 @@
   "formatVersion": 1,
   "database": {
     "version": 1,
-    "identityHash": "16167d0ed0bd86c6fc41266aa0abe921",
+    "identityHash": "71fabb860e0ba05f19f248efc40ff07d",
     "entities": [
       {
         "ftsVersion": "FTS4",
@@ -18,7 +18,7 @@
         },
         "contentSyncTriggers": [],
         "tableName": "WarmongerEntity",
-        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`cyrillicName` TEXT NOT NULL, `name` TEXT NOT NULL, `notes` TEXT NOT NULL, tokenize=unicode61)",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`cyrillicName` TEXT NOT NULL, `name` TEXT NOT NULL, `notes` TEXT NOT NULL, `tags` TEXT NOT NULL, tokenize=unicode61)",
         "fields": [
           {
             "fieldPath": "cyrillicName",
@@ -37,6 +37,12 @@
             "columnName": "notes",
             "affinity": "TEXT",
             "notNull": true
+          },
+          {
+            "fieldPath": "tags",
+            "columnName": "tags",
+            "affinity": "TEXT",
+            "notNull": true
           }
         ],
         "primaryKey": {
@@ -71,12 +77,70 @@
         },
         "indices": [],
         "foreignKeys": []
+      },
+      {
+        "tableName": "TagEntity",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `ruName` TEXT NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "ruName",
+            "columnName": "ruName",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "IndexEntity",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY(`key`))",
+        "fields": [
+          {
+            "fieldPath": "key",
+            "columnName": "key",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "value",
+            "columnName": "value",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "key"
+          ],
+          "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, '16167d0ed0bd86c6fc41266aa0abe921')"
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '71fabb860e0ba05f19f248efc40ff07d')"
     ]
   }
 }

+ 6 - 2
app/schemas/init.sql

@@ -1,6 +1,10 @@
 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, '16167d0ed0bd86c6fc41266aa0abe921');
+INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '71fabb860e0ba05f19f248efc40ff07d');
 CREATE TABLE IF NOT EXISTS `MetaEntity` (`key` TEXT NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY(`key`));
 .import --csv app/schemas/MetaEntity.csv MetaEntity
-CREATE VIRTUAL TABLE IF NOT EXISTS `WarmongerEntity` USING FTS4(`cyrillicName` TEXT NOT NULL, `name` TEXT NOT NULL, `notes` TEXT NOT NULL, tokenize=unicode61);
+CREATE VIRTUAL TABLE IF NOT EXISTS `WarmongerEntity` USING FTS4(`cyrillicName` TEXT NOT NULL, `name` TEXT NOT NULL, `notes` TEXT NOT NULL, `tags` TEXT NOT NULL, tokenize=unicode61);
 .import --csv app/schemas/WarmongerEntity.csv WarmongerEntity
+CREATE TABLE IF NOT EXISTS `TagEntity` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `ruName` TEXT NOT NULL, PRIMARY KEY(`id`));
+.import --csv app/schemas/TagEntity.csv TagEntity
+CREATE TABLE IF NOT EXISTS `IndexEntity` (`key` TEXT NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY(`key`));
+.import --csv app/schemas/IndexEntity.csv IndexEntity

BIN
app/src/main/assets/warmongers.db


+ 1 - 1
app/src/main/java/com/sirekanian/acf/App.kt

@@ -13,5 +13,5 @@ class App : Application() {
             .createFromAsset("warmongers.db")
             .build()
     }
-    val repository: Repository by lazy { RepositoryImpl(db.getWarmongerDao()) }
+    val repository: Repository by lazy { RepositoryImpl(db.getWarmongerDao(), db.getTagDao()) }
 }

+ 19 - 5
app/src/main/java/com/sirekanian/acf/D.kt

@@ -1,21 +1,35 @@
 package com.sirekanian.acf
 
 import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 
 object D {
     val listPaddings = PaddingValues(16.dp, 8.dp, 16.dp, 16.dp)
-    val toolbarSize = 64.dp
+    val tagsPaddings = PaddingValues(16.dp, 0.dp, 16.dp, 8.dp)
+    val tagsSpacing = 8.dp
+    val tagHeight = 32.dp
+    val tagShape = RoundedCornerShape(8.dp)
+    val tagBorder = 1.dp
+    val tagPaddings = 8.dp
+    val tagIconSize = 18.dp
+    val toolbarHeaderSize = 64.dp
+    val toolbarSize: Dp =
+        toolbarHeaderSize + tagHeight +
+                tagsPaddings.calculateTopPadding() +
+                tagsPaddings.calculateBottomPadding()
     val toolbarElevation = 8.dp
     val toolbarButtonPadding = 16.dp
+    val fabPadding = 16.dp
     val fabSize = 96.dp
+    val fabShape = RoundedCornerShape(28.dp)
     val fabIconSize = 36.dp
-    val fabPadding = 16.dp
-    val cornerSizeSmall = 12.dp
-    val cornerSizeMedium = 28.dp
+    val cardsSpacing = 8.dp
+    val cardDefaultCornerSize = 28.dp
+    val cardSelectedCornerSize = 12.dp
     val cardDefaultElevation = 2.dp
     val cardSelectedElevation = 4.dp
-    val cardSpacing = 8.dp
     val progressSize = 2.dp
     val dialogTitlePadding = 16.dp
     val menuIconPadding = 16.dp

+ 11 - 2
app/src/main/java/com/sirekanian/acf/MainActivity.kt

@@ -31,7 +31,8 @@ class MainActivity : ComponentActivity() {
             val isCyrillic = isCyrillicResources()
             val state = remember { MainState(coroutineScope, isCyrillic) }
             val presenter = remember { createPresenter(app(), state) }
-            val data by presenter.observeData().collectAsState(listOf())
+            val data: List<WarmongerModel> by presenter.observeData().collectAsState(listOf())
+            val tags: List<TagModel> by produceState(listOf()) { value = presenter.getTags() }
             val hasData by derivedStateOf { data.isNotEmpty() }
             BackHandler(enabled = state.search.isOpened) {
                 state.search.isOpened = false
@@ -45,7 +46,15 @@ class MainActivity : ComponentActivity() {
                     MainLayout(
                         state = state,
                         toolbar = { insets ->
-                            MainToolbar(insets, state.search)
+                            Column(
+                                modifier = Modifier
+                                    .padding(insets)
+                                    .fillMaxWidth()
+                                    .height(D.toolbarSize)
+                            ) {
+                                MainToolbar(state.search)
+                                MainTags(state.search, tags)
+                            }
                             MainProgress(insets, state.progress)
                         },
                         toolbarElevation = state.toolbarElevation,

+ 7 - 5
app/src/main/java/com/sirekanian/acf/MainPresenter.kt

@@ -1,6 +1,7 @@
 package com.sirekanian.acf
 
 import com.sirekanian.acf.data.Repository
+import com.sirekanian.acf.data.Tag
 import com.sirekanian.acf.data.Warmonger
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.flow.Flow
@@ -12,6 +13,7 @@ fun createPresenter(app: App, state: MainState): MainPresenter =
 
 interface MainPresenter {
     fun observeData(): Flow<List<WarmongerModel>>
+    suspend fun getTags(): List<TagModel>
     suspend fun updateData()
 }
 
@@ -21,16 +23,16 @@ class MainPresenterImpl(
 ) : MainPresenter {
 
     override fun observeData(): Flow<List<WarmongerModel>> =
-        if (state.search.isOpened) {
-            repository.observeByQuery(state.search.query.text)
-        } else {
-            repository.observeAll()
-        }.map { warmongers ->
+        repository.observeByQuery(state.search.fullQuery).map { warmongers ->
             warmongers.map { warmonger ->
                 Warmonger.toModel(warmonger, state.isCyrillic)
             }
         }
 
+    override suspend fun getTags(): List<TagModel> =
+        repository.getTags().map { Tag.toModel(it, state.isCyrillic) }
+
+    @Suppress("unused") // TODO: 1202468796234411
     override suspend fun updateData() =
         withContext(IO) {
             repository.updateFromRemote(state.progress)

+ 10 - 0
app/src/main/java/com/sirekanian/acf/MainState.kt

@@ -36,8 +36,18 @@ class ListState {
 }
 
 class SearchState {
+    var tag by mutableStateOf<Int?>(null)
     var query by mutableStateOf(TextFieldValue())
     var isOpened by mutableStateOf(false)
+    val fullQuery: String? by derivedStateOf {
+        if (isOpened) {
+            val textQuery = if (query.text.isEmpty()) null else "${query.text}*"
+            val tagsQuery = if (tag == null) null else "tags:$tag"
+            listOfNotNull(textQuery, tagsQuery).joinToString(" ")
+        } else {
+            tag?.let { "tags:$it" }
+        }
+    }
 }
 
 class ProgressState {

+ 3 - 0
app/src/main/java/com/sirekanian/acf/TagModel.kt

@@ -0,0 +1,3 @@
+package com.sirekanian.acf
+
+class TagModel(val id: Int, val name: String)

+ 14 - 6
app/src/main/java/com/sirekanian/acf/data/Repository.kt

@@ -2,32 +2,40 @@ package com.sirekanian.acf.data
 
 import android.database.sqlite.SQLiteException
 import com.sirekanian.acf.ProgressState
+import com.sirekanian.acf.data.local.TagDao
 import com.sirekanian.acf.data.local.WarmongerDao
 import com.sirekanian.acf.data.local.WarmongerEntity
 import com.sirekanian.acf.data.remote.getWarmongers
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 
 interface Repository {
-    fun observeAll(): Flow<List<Warmonger>>
-    fun observeByQuery(query: String): Flow<List<Warmonger>>
+    fun observeByQuery(query: String?): Flow<List<Warmonger>>
+    suspend fun getTags(): List<Tag>
     suspend fun updateFromRemote(progressState: ProgressState)
 }
 
 class RepositoryImpl(
     private val dao: WarmongerDao,
+    private val tagDao: TagDao,
 ) : Repository {
 
-    override fun observeAll(): Flow<List<Warmonger>> =
-        dao.observeAll().fromEntities()
+    override fun observeByQuery(query: String?): Flow<List<Warmonger>> =
+        when {
+            query == null -> dao.observeAll()
+            query.isEmpty() -> flowOf(listOf())
+            else -> dao.observeByQuery(query)
+        }.fromEntities()
 
-    override fun observeByQuery(query: String): Flow<List<Warmonger>> =
-        dao.observeByQuery("$query*").fromEntities()
+    override suspend fun getTags(): List<Tag> =
+        tagDao.getTags().map(Tag::fromEntity)
 
     private fun Flow<List<WarmongerEntity>>.fromEntities(): Flow<List<Warmonger>> =
         map { it.map(Warmonger::fromEntity) }.catch {
             if (it is SQLiteException) {
+                // TODO: 1202308718694574
                 emit(listOf())
             } else {
                 throw it

+ 29 - 0
app/src/main/java/com/sirekanian/acf/data/Tag.kt

@@ -0,0 +1,29 @@
+package com.sirekanian.acf.data
+
+import com.sirekanian.acf.TagModel
+import com.sirekanian.acf.data.local.TagEntity
+
+class Tag(
+    val id: Int,
+    val name: String,
+    val ruName: String,
+) {
+
+    companion object {
+
+        fun fromEntity(entity: TagEntity): Tag =
+            Tag(
+                id = entity.id,
+                name = entity.name,
+                ruName = entity.ruName,
+            )
+
+        fun toModel(tag: Tag, isCyrillic: Boolean): TagModel =
+            TagModel(
+                id = tag.id,
+                name = if (isCyrillic) tag.ruName else tag.name
+            )
+
+    }
+
+}

+ 1 - 0
app/src/main/java/com/sirekanian/acf/data/Warmonger.kt

@@ -31,6 +31,7 @@ class Warmonger(
                 cyrillicName = warmonger.cyrillicName,
                 name = warmonger.name,
                 notes = warmonger.notes,
+                tags = "" // TODO: 1202468796234411
             )
 
         fun toModel(warmonger: Warmonger, isCyrillic: Boolean): WarmongerModel =

+ 10 - 1
app/src/main/java/com/sirekanian/acf/data/local/Database.kt

@@ -3,10 +3,19 @@ package com.sirekanian.acf.data.local
 import androidx.room.Database
 import androidx.room.RoomDatabase
 
-@Database(entities = [WarmongerEntity::class, MetaEntity::class], version = 1)
+@Database(
+    entities = [
+        WarmongerEntity::class,
+        MetaEntity::class,
+        TagEntity::class,
+        IndexEntity::class,
+    ],
+    version = 1,
+)
 abstract class Database : RoomDatabase() {
 
     abstract fun getWarmongerDao(): WarmongerDao
     abstract fun getMetaDao(): MetaDao
+    abstract fun getTagDao(): TagDao
 
 }

+ 11 - 0
app/src/main/java/com/sirekanian/acf/data/local/IndexEntity.kt

@@ -0,0 +1,11 @@
+package com.sirekanian.acf.data.local
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+class IndexEntity(
+    @PrimaryKey
+    val key: String,
+    val value: String,
+)

+ 12 - 0
app/src/main/java/com/sirekanian/acf/data/local/TagDao.kt

@@ -0,0 +1,12 @@
+package com.sirekanian.acf.data.local
+
+import androidx.room.Dao
+import androidx.room.Query
+
+@Dao
+interface TagDao {
+
+    @Query("SELECT * FROM TagEntity")
+    suspend fun getTags(): List<TagEntity>
+
+}

+ 12 - 0
app/src/main/java/com/sirekanian/acf/data/local/TagEntity.kt

@@ -0,0 +1,12 @@
+package com.sirekanian.acf.data.local
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+class TagEntity(
+    @PrimaryKey
+    val id: Int,
+    val name: String,
+    val ruName: String,
+)

+ 1 - 0
app/src/main/java/com/sirekanian/acf/data/local/WarmongerEntity.kt

@@ -10,4 +10,5 @@ class WarmongerEntity(
     val cyrillicName: String,
     val name: String,
     val notes: String,
+    val tags: String,
 )

+ 2 - 2
app/src/main/java/com/sirekanian/acf/data/remote/WarmongerDto.kt

@@ -3,11 +3,11 @@ package com.sirekanian.acf.data.remote
 import kotlinx.serialization.Serializable
 
 @Serializable
-data class WarmongerDto(
+class WarmongerDto(
     val `0`: String,
     val `1`: String,
     val `2`: String,
     val `3`: String,
     val `4`: String,
-    val `5`: String,
+    val `5`: Set<Int>,
 )

+ 12 - 3
app/src/main/java/com/sirekanian/acf/ext/AnimatedVisibility.kt

@@ -1,12 +1,21 @@
 package com.sirekanian.acf.ext
 
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
+import androidx.compose.animation.*
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
 
 @Composable
 fun DefaultAnimatedVisibility(visible: Boolean, content: @Composable () -> Unit) =
     AnimatedVisibility(visible = visible, enter = fadeIn(), exit = fadeOut()) {
         content()
     }
+
+@Composable
+fun AdvancedAnimatedVisibility(visible: Boolean, content: @Composable () -> Unit) =
+    AnimatedVisibility(
+        visible = visible,
+        enter = fadeIn() + expandIn(expandFrom = Alignment.Center),
+        exit = fadeOut() + shrinkOut(shrinkTowards = Alignment.Center),
+    ) {
+        content()
+    }

+ 8 - 0
app/src/main/java/com/sirekanian/acf/ext/Color.kt

@@ -0,0 +1,8 @@
+package com.sirekanian.acf.ext
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.core.graphics.ColorUtils
+
+fun Color.isLightColor(): Boolean =
+    ColorUtils.calculateLuminance(toArgb()) < 0.18

+ 1 - 1
app/src/main/java/com/sirekanian/acf/ui/MainContent.kt

@@ -26,7 +26,7 @@ fun MainContent(insets: PaddingValues, state: MainState, data: List<WarmongerMod
             .pointerInputOnDown(Unit) { focusManager.clearFocus() },
         state = state.list.lazyListState,
         contentPadding = insets + paddings + D.listPaddings,
-        verticalArrangement = Arrangement.spacedBy(D.cardSpacing)
+        verticalArrangement = Arrangement.spacedBy(D.cardsSpacing)
     ) {
         items(data) { item ->
             WarmongerCard(state.dialog, item)

+ 1 - 2
app/src/main/java/com/sirekanian/acf/ui/MainFab.kt

@@ -3,7 +3,6 @@ package com.sirekanian.acf.ui
 import androidx.compose.foundation.layout.size
 import androidx.compose.material.FloatingActionButton
 import androidx.compose.material.Icon
-import androidx.compose.material.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
@@ -16,7 +15,7 @@ fun MainFab(onClick: () -> Unit) {
     FloatingActionButton(
         onClick = onClick,
         modifier = Modifier.size(D.fabSize),
-        shape = MaterialTheme.shapes.medium,
+        shape = D.fabShape,
     ) {
         Icon(
             imageVector = IconSearch,

+ 68 - 0
app/src/main/java/com/sirekanian/acf/ui/MainTags.kt

@@ -0,0 +1,68 @@
+package com.sirekanian.acf.ui
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextOverflow
+import com.sirekanian.acf.D
+import com.sirekanian.acf.SearchState
+import com.sirekanian.acf.TagModel
+import com.sirekanian.acf.ext.AdvancedAnimatedVisibility
+import com.sirekanian.acf.ui.icons.IconDone
+
+@Composable
+fun MainTags(searchState: SearchState, tags: List<TagModel>) {
+    LazyRow(
+        contentPadding = D.tagsPaddings,
+        horizontalArrangement = Arrangement.spacedBy(D.tagsSpacing),
+    ) {
+        items(tags, key = { it.id }) { tag ->
+            val selected = tag.id == searchState.tag
+            TagView(
+                tag = tag,
+                selected = selected,
+                onClick = { searchState.tag = if (selected) null else tag.id }
+            )
+        }
+    }
+}
+
+@Composable
+private fun TagView(tag: TagModel, selected: Boolean, onClick: () -> Unit) {
+    val color = if (selected) MaterialTheme.colors.secondary else MaterialTheme.colors.surface
+    val border = if (selected) null else BorderStroke(D.tagBorder, contentColorFor(color))
+    Surface(
+        shape = D.tagShape,
+        color = color,
+        border = border,
+    ) {
+        Row(
+            Modifier
+                .clickable(onClick = onClick)
+                .height(D.tagHeight)
+                .padding(horizontal = D.tagPaddings),
+            verticalAlignment = Alignment.CenterVertically,
+        ) {
+            AdvancedAnimatedVisibility(selected) {
+                Icon(
+                    modifier = Modifier.size(D.tagIconSize),
+                    imageVector = IconDone,
+                    contentDescription = null,
+                )
+            }
+            Text(
+                modifier = Modifier.padding(horizontal = D.tagPaddings),
+                text = tag.name,
+                style = MaterialTheme.typography.body2,
+                maxLines = 1,
+                overflow = TextOverflow.Ellipsis,
+            )
+        }
+    }
+}

+ 4 - 4
app/src/main/java/com/sirekanian/acf/ui/MainToolbar.kt

@@ -1,7 +1,7 @@
 package com.sirekanian.acf.ui
 
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material.MaterialTheme
@@ -20,11 +20,11 @@ import com.sirekanian.acf.R
 import com.sirekanian.acf.SearchState
 
 @Composable
-fun MainToolbar(insets: PaddingValues, searchState: SearchState) {
+fun MainToolbar(searchState: SearchState) {
     Box(
         modifier = Modifier
-            .padding(insets)
-            .height(D.toolbarSize),
+            .fillMaxWidth()
+            .height(D.toolbarHeaderSize),
         contentAlignment = Alignment.Center,
     ) {
         if (searchState.isOpened) {

+ 2 - 2
app/src/main/java/com/sirekanian/acf/ui/WarmongerCard.kt

@@ -24,9 +24,9 @@ fun WarmongerCard(dialogState: DialogState, warmonger: WarmongerModel) {
     var isExpanded by remember { mutableStateOf(false) }
     val surfaceCornerSize by animateDpAsState(
         if (isExpanded) {
-            D.cornerSizeSmall
+            D.cardSelectedCornerSize
         } else {
-            D.cornerSizeMedium
+            D.cardDefaultCornerSize
         }
     )
     val surfaceElevation by animateDpAsState(

+ 19 - 0
app/src/main/java/com/sirekanian/acf/ui/icons/IconDone.kt

@@ -0,0 +1,19 @@
+package com.sirekanian.acf.ui.icons
+
+import androidx.compose.material.icons.materialIcon
+import androidx.compose.material.icons.materialPath
+import androidx.compose.ui.graphics.vector.ImageVector
+
+val IconDone: ImageVector =
+    materialIcon(name = "Filled.Done") {
+        materialPath {
+            moveTo(9.0f, 16.2f)
+            lineTo(4.8f, 12.0f)
+            lineToRelative(-1.4f, 1.4f)
+            lineTo(9.0f, 19.0f)
+            lineTo(21.0f, 7.0f)
+            lineToRelative(-1.4f, -1.4f)
+            lineTo(9.0f, 16.2f)
+            close()
+        }
+    }

+ 2 - 3
app/src/main/java/com/sirekanian/acf/ui/theme/Shape.kt

@@ -3,10 +3,9 @@ package com.sirekanian.acf.ui.theme
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.Shapes
 import androidx.compose.ui.unit.dp
-import com.sirekanian.acf.D
 
 val Shapes = Shapes(
-    small = RoundedCornerShape(D.cornerSizeSmall),
-    medium = RoundedCornerShape(D.cornerSizeMedium),
+    small = RoundedCornerShape(0.dp),
+    medium = RoundedCornerShape(0.dp),
     large = RoundedCornerShape(0.dp)
 )

+ 0 - 11
app/src/main/java/com/sirekanian/acf/ui/theme/Type.kt

@@ -1,19 +1,8 @@
 package com.sirekanian.acf.ui.theme
 
 import androidx.compose.material.Typography
-import androidx.compose.ui.text.font.Font
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontWeight
-import com.sirekanian.acf.R
-
-private val customFontFamily = FontFamily(
-    Font(R.font.montserrat_light, FontWeight.Light),
-    Font(R.font.montserrat_regular, FontWeight.Normal),
-    Font(R.font.montserrat_medium, FontWeight.Medium),
-)
 
 val Typography = Typography(
-    customFontFamily,
     h1 = Type3.displayLarge,
     h2 = Type3.displayMedium,
     h3 = Type3.headlineLarge,

+ 18 - 0
app/src/main/java/com/sirekanian/acf/ui/theme/Type3.kt

@@ -3,59 +3,77 @@
 package com.sirekanian.acf.ui.theme
 
 import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.sp
+import com.sirekanian.acf.R
+
+private val customFontFamily = FontFamily(
+    Font(R.font.montserrat_light, FontWeight.Light),
+    Font(R.font.montserrat_regular, FontWeight.Normal),
+    Font(R.font.montserrat_medium, FontWeight.Medium),
+)
 
 object Type3 {
     val displayLarge = TextStyle(
+        fontFamily = customFontFamily,
         fontWeight = FontWeight.Normal,
         fontSize = 57.sp,
         lineHeight = 64.sp,
         letterSpacing = (-0.25).sp,
     )
     val displayMedium = TextStyle(
+        fontFamily = customFontFamily,
         fontWeight = FontWeight.Normal,
         fontSize = 45.sp,
         lineHeight = 52.sp,
         letterSpacing = 0.sp,
     )
     val displaySmall = TextStyle(
+        fontFamily = customFontFamily,
         fontWeight = FontWeight.Normal,
         fontSize = 36.sp,
         lineHeight = 44.sp,
         letterSpacing = 0.sp,
     )
     val headlineLarge = TextStyle(
+        fontFamily = customFontFamily,
         fontWeight = FontWeight.Normal,
         fontSize = 32.sp,
         lineHeight = 40.sp,
         letterSpacing = 0.sp,
     )
     val headlineMedium = TextStyle(
+        fontFamily = customFontFamily,
         fontWeight = FontWeight.Normal,
         fontSize = 28.sp,
         lineHeight = 36.sp,
         letterSpacing = 0.sp,
     )
     val headlineSmall = TextStyle(
+        fontFamily = customFontFamily,
         fontWeight = FontWeight.Normal,
         fontSize = 24.sp,
         lineHeight = 32.sp,
         letterSpacing = 0.sp,
     )
     val titleLarge = TextStyle(
+        fontFamily = customFontFamily,
         fontWeight = FontWeight.Normal,
         fontSize = 22.sp,
         lineHeight = 28.sp,
         letterSpacing = 0.sp,
     )
     val titleMedium = TextStyle(
+        fontFamily = customFontFamily,
         fontWeight = FontWeight.Medium,
         fontSize = 16.sp,
         lineHeight = 24.sp,
         letterSpacing = 0.1.sp,
     )
     val titleSmall = TextStyle(
+        fontFamily = customFontFamily,
         fontWeight = FontWeight.Medium,
         fontSize = 14.sp,
         lineHeight = 20.sp,

+ 2 - 2
build.gradle

@@ -1,8 +1,8 @@
 buildscript {
     ext {
-        androidPluginVersion = '7.2.0'
+        androidPluginVersion = '7.2.1'
         kotlinVersion = '1.6.21'
-        composeVersion = '1.2.0-beta02'
+        composeVersion = '1.2.0-beta03'
         roomVersion = '2.4.2'
         ktorVersion = '2.0.1'
     }

+ 12 - 2
update.sh

@@ -13,15 +13,25 @@ wget -qO- "$ENDPOINT/meta.json" |
 
 # transform data.json to csv
 wget --header="Accept-Encoding: gzip" -qO- "$ENDPOINT/data.json" | gunzip |
-  jq -r 'map([.["0"],.["1"],.["4"]])[] | @csv' \
+  jq -r 'map([.["0"],.["1"],.["4"],(.["5"] | join(" "))])[] | @csv' \
     >"app/schemas/WarmongerEntity.csv"
 
+# transform tags.json to csv
+wget -qO- "$ENDPOINT/tags.json" |
+  jq -r 'map([.["id"],.["shortName"],.["ruShortName"]])[] | @csv' \
+    >"app/schemas/TagEntity.csv"
+
+# transform index.json to csv
+wget -qO- "$ENDPOINT/index.json" |
+  jq -r 'to_entries[] | [.key, .value] | @csv' \
+    >"app/schemas/IndexEntity.csv"
+
 # copy setupQueries from schema
 jq -r ".database.setupQueries[]" "$SCHEMA" |
   sed 's/$/;/' \
     >app/schemas/init.sql
 
-for TABLE in MetaEntity WarmongerEntity; do
+for TABLE in MetaEntity WarmongerEntity TagEntity IndexEntity; do
 
   # copy createSql from schema
   jq -r ".database.entities[] | select(.tableName==\"$TABLE\") | .createSql" "$SCHEMA" |

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott