Jelajahi Sumber

Reformatted code

sirekanyan 8 tahun lalu
induk
melakukan
48097ba26c

+ 28 - 24
app/src/main/java/me/vadik/knigopis/App.kt

@@ -15,29 +15,33 @@ private const val IMAGE_API_URL = "https://api.qwant.com/api/"
 
 class App : Application() {
 
-  val baseApi: Retrofit by lazy {
-    Retrofit.Builder()
-        .baseUrl(MAIN_API_URL)
-        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
-        .addConverterFactory(GsonConverterFactory.create(
-            GsonBuilder().registerTypeAdapter(
-                Notes::class.java,
-                NotesTypeAdapter()
-            ).create()
-        ))
-        .build()
-  }
+    val baseApi: Retrofit by lazy {
+        Retrofit.Builder()
+            .baseUrl(MAIN_API_URL)
+            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+            .addConverterFactory(
+                GsonConverterFactory.create(
+                    GsonBuilder().registerTypeAdapter(
+                        Notes::class.java,
+                        NotesTypeAdapter()
+                    ).create()
+                )
+            )
+            .build()
+    }
 
-  val imageApi: Retrofit by lazy {
-    Retrofit.Builder()
-        .baseUrl(IMAGE_API_URL)
-        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
-        .addConverterFactory(GsonConverterFactory.create(
-            GsonBuilder().registerTypeAdapter(
-                ImageThumbnail::class.java,
-                ImageThumbnailDeserializer()
-            ).create()
-        ))
-        .build()
-  }
+    val imageApi: Retrofit by lazy {
+        Retrofit.Builder()
+            .baseUrl(IMAGE_API_URL)
+            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+            .addConverterFactory(
+                GsonConverterFactory.create(
+                    GsonBuilder().registerTypeAdapter(
+                        ImageThumbnail::class.java,
+                        ImageThumbnailDeserializer()
+                    ).create()
+                )
+            )
+            .build()
+    }
 }

+ 124 - 120
app/src/main/java/me/vadik/knigopis/BookActivity.kt

@@ -28,131 +28,135 @@ fun Context.createEditBookIntent(bookId: String, finished: Boolean): Intent =
 
 class BookActivity : AppCompatActivity() {
 
-  private val config by lazy { ConfigurationImpl(applicationContext) as Configuration }
-  private val api by lazy { app().baseApi.create(Endpoint::class.java) }
-  private val repository by lazy {
-    val auth = KAuthImpl(applicationContext, api)
-    if (config.isDevMode()) {
-      BookRepositoryMock()
-    } else {
-      BookRepositoryImpl(api, auth)
-    }
-  }
-  private val imageSearch: BookCoverSearch by lazy {
-    BookCoverSearchImpl(
-        app().imageApi.create(ImageEndpoint::class.java),
-        BookCoverCacheImpl(applicationContext)
-    )
-  }
-  private var bookId: String? = null
-
-  override fun onCreate(savedInstanceState: Bundle?) {
-    super.onCreate(savedInstanceState)
-    setContentView(R.layout.book_edit)
-    bookId = intent.getStringExtra(EXTRA_BOOK_ID)
-    toolbar.inflateMenu(R.menu.book_menu)
-    if (bookId == null) titleEditText.requestFocus()
-    toolbar.setTitle(if (bookId == null) R.string.book_add_title else R.string.book_edit_title)
-    toolbar.setNavigationIcon(R.drawable.ic_arrow_back)
-    toolbar.setNavigationOnClickListener {
-      finish()
-    }
-    val progressMenuItem = toolbar.menu.findItem(R.id.option_progress_bar)
-    toolbar.setOnMenuItemClickListener { saveMenuItem ->
-      when (saveMenuItem.itemId) {
-        R.id.option_save_book -> {
-          hideKeyboard()
-          if (readCheckbox.isChecked) {
-            repository.saveBook(bookId, FinishedBookToSend(
-                titleEditText.text.toString(),
-                authorEditText.text.toString(),
-                dayEditText.text.toString(),
-                monthEditText.text.toString(),
-                yearEditText.text.toString(),
-                notesTextArea.text.toString()
-            ))
-          } else {
-            repository.saveBook(bookId, PlannedBookToSend(
-                titleEditText.text.toString(),
-                authorEditText.text.toString(),
-                Notes(
-                    notesTextArea.text.toString(),
-                    0 // todo: actual progress
-                )
-            ))
-          }.io2main()
-              .doOnSubscribe {
-                saveMenuItem.isVisible = false
-                progressMenuItem.isVisible = true
-                progressMenuItem.actionView.show()
-              }
-              .doOnError {
-                progressMenuItem.isVisible = false
-                progressMenuItem.actionView.hide()
-                saveMenuItem.isVisible = true
-              }
-              .subscribe({
-                setResult(RESULT_OK)
-                finish()
-              }, {
-                toast("Ошибка при сохранении книги")
-                logError("cannot post planned book", it)
-              })
-          true
+    private val config by lazy { ConfigurationImpl(applicationContext) as Configuration }
+    private val api by lazy { app().baseApi.create(Endpoint::class.java) }
+    private val repository by lazy {
+        val auth = KAuthImpl(applicationContext, api)
+        if (config.isDevMode()) {
+            BookRepositoryMock()
+        } else {
+            BookRepositoryImpl(api, auth)
         }
-        else -> false
-      }
     }
-    coverImageViews.offscreenPageLimit = IMAGE_PRELOAD_COUNT
-    titleEditText.setOnFocusChangeListener { _, focus ->
-      val editable = titleEditText.editableText
-      if (!focus && !editable.isEmpty()) {
-        imageSearch.search(editable.toString())
-            .subscribe({ urls ->
-              coverImageViews.visibility = INVISIBLE
-              coverImageViews.adapter = CoverPagerAdapter(urls,
-                  onClick = { position, last ->
-                    coverImageViews.currentItem = if (last) 0 else position + 1
-                  },
-                  onFirstLoaded = {
-                    coverImageViews.visibility = VISIBLE
-                  })
-            }, {
-              logError("cannot load thumbnail", it)
-            })
-      }
+    private val imageSearch: BookCoverSearch by lazy {
+        BookCoverSearchImpl(
+            app().imageApi.create(ImageEndpoint::class.java),
+            BookCoverCacheImpl(applicationContext)
+        )
     }
-    readCheckbox.setOnCheckedChangeListener { _, checked ->
-      arrayOf(bookDayInput, bookMonthInput, bookYearInput).forEach { view ->
-        view.visibility = if (checked) VISIBLE else GONE
-      }
-    }
-  }
+    private var bookId: String? = null
 
-  override fun onStart() {
-    super.onStart()
-    val finished = intent.getBooleanExtra(EXTRA_BOOK_FINISHED, false)
-    bookId?.let { id ->
-      if (finished) {
-        api.getFinishedBook(id)
-            .io2main()
-            .doOnSuccess { finishedBook ->
-              readCheckbox.isChecked = true
-              yearEditText.setText(finishedBook.readYear)
-              monthEditText.setText(finishedBook.readMonth)
-              dayEditText.setText(finishedBook.readDay)
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.book_edit)
+        bookId = intent.getStringExtra(EXTRA_BOOK_ID)
+        toolbar.inflateMenu(R.menu.book_menu)
+        if (bookId == null) titleEditText.requestFocus()
+        toolbar.setTitle(if (bookId == null) R.string.book_add_title else R.string.book_edit_title)
+        toolbar.setNavigationIcon(R.drawable.ic_arrow_back)
+        toolbar.setNavigationOnClickListener {
+            finish()
+        }
+        val progressMenuItem = toolbar.menu.findItem(R.id.option_progress_bar)
+        toolbar.setOnMenuItemClickListener { saveMenuItem ->
+            when (saveMenuItem.itemId) {
+                R.id.option_save_book -> {
+                    hideKeyboard()
+                    if (readCheckbox.isChecked) {
+                        repository.saveBook(
+                            bookId, FinishedBookToSend(
+                                titleEditText.text.toString(),
+                                authorEditText.text.toString(),
+                                dayEditText.text.toString(),
+                                monthEditText.text.toString(),
+                                yearEditText.text.toString(),
+                                notesTextArea.text.toString()
+                            )
+                        )
+                    } else {
+                        repository.saveBook(
+                            bookId, PlannedBookToSend(
+                                titleEditText.text.toString(),
+                                authorEditText.text.toString(),
+                                Notes(
+                                    notesTextArea.text.toString(),
+                                    0 // todo: actual progress
+                                )
+                            )
+                        )
+                    }.io2main()
+                        .doOnSubscribe {
+                            saveMenuItem.isVisible = false
+                            progressMenuItem.isVisible = true
+                            progressMenuItem.actionView.show()
+                        }
+                        .doOnError {
+                            progressMenuItem.isVisible = false
+                            progressMenuItem.actionView.hide()
+                            saveMenuItem.isVisible = true
+                        }
+                        .subscribe({
+                            setResult(RESULT_OK)
+                            finish()
+                        }, {
+                            toast("Ошибка при сохранении книги")
+                            logError("cannot post planned book", it)
+                        })
+                    true
+                }
+                else -> false
+            }
+        }
+        coverImageViews.offscreenPageLimit = IMAGE_PRELOAD_COUNT
+        titleEditText.setOnFocusChangeListener { _, focus ->
+            val editable = titleEditText.editableText
+            if (!focus && !editable.isEmpty()) {
+                imageSearch.search(editable.toString())
+                    .subscribe({ urls ->
+                        coverImageViews.visibility = INVISIBLE
+                        coverImageViews.adapter = CoverPagerAdapter(urls,
+                            onClick = { position, last ->
+                                coverImageViews.currentItem = if (last) 0 else position + 1
+                            },
+                            onFirstLoaded = {
+                                coverImageViews.visibility = VISIBLE
+                            })
+                    }, {
+                        logError("cannot load thumbnail", it)
+                    })
             }
-      } else {
-        api.getPlannedBook(id)
-            .io2main()
-            .doOnSuccess { plannedBook ->
-              readCheckbox.isChecked = false
-              notesTextArea.setText(plannedBook.notes.text)
+        }
+        readCheckbox.setOnCheckedChangeListener { _, checked ->
+            arrayOf(bookDayInput, bookMonthInput, bookYearInput).forEach { view ->
+                view.visibility = if (checked) VISIBLE else GONE
             }
-      }.subscribe({ book ->
-        titleEditText.setText(book.title)
-        authorEditText.setText(book.author)
-      }, { logError("cannot get planned book", it) })
+        }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        val finished = intent.getBooleanExtra(EXTRA_BOOK_FINISHED, false)
+        bookId?.let { id ->
+            if (finished) {
+                api.getFinishedBook(id)
+                    .io2main()
+                    .doOnSuccess { finishedBook ->
+                        readCheckbox.isChecked = true
+                        yearEditText.setText(finishedBook.readYear)
+                        monthEditText.setText(finishedBook.readMonth)
+                        dayEditText.setText(finishedBook.readDay)
+                    }
+            } else {
+                api.getPlannedBook(id)
+                    .io2main()
+                    .doOnSuccess { plannedBook ->
+                        readCheckbox.isChecked = false
+                        notesTextArea.setText(plannedBook.notes.text)
+                    }
+            }.subscribe({ book ->
+                titleEditText.setText(book.title)
+                authorEditText.setText(book.author)
+            }, { logError("cannot get planned book", it) })
+        }
     }
-  }
 }

+ 9 - 9
app/src/main/java/me/vadik/knigopis/BookCoverCache.kt

@@ -7,19 +7,19 @@ import io.reactivex.Maybe
 private const val PREFERENCES_NAME = "knigopis_thumbnails"
 
 interface BookCoverCache {
-  fun put(bookId: String, url: String)
-  fun find(bookId: String): Maybe<String>
+    fun put(bookId: String, url: String)
+    fun find(bookId: String): Maybe<String>
 }
 
 class BookCoverCacheImpl(context: Context) : BookCoverCache {
 
-  private val preferences = context.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE)
+    private val preferences = context.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE)
 
-  override fun put(bookId: String, url: String) =
-      preferences.edit().putString(bookId, url).apply()
+    override fun put(bookId: String, url: String) =
+        preferences.edit().putString(bookId, url).apply()
 
-  override fun find(bookId: String): Maybe<String> =
-      Maybe.fromCallable {
-        preferences.getString(bookId, null)
-      }
+    override fun find(bookId: String): Maybe<String> =
+        Maybe.fromCallable {
+            preferences.getString(bookId, null)
+        }
 }

+ 19 - 19
app/src/main/java/me/vadik/knigopis/BookRepository.kt

@@ -7,8 +7,8 @@ import me.vadik.knigopis.model.FinishedBookToSend
 import me.vadik.knigopis.model.PlannedBookToSend
 
 interface BookRepository {
-  fun saveBook(bookId: String?, book: FinishedBookToSend): Completable
-  fun saveBook(bookId: String?, book: PlannedBookToSend): Completable
+    fun saveBook(bookId: String?, book: FinishedBookToSend): Completable
+    fun saveBook(bookId: String?, book: PlannedBookToSend): Completable
 }
 
 class BookRepositoryImpl(
@@ -16,26 +16,26 @@ class BookRepositoryImpl(
     private val auth: KAuth
 ) : BookRepository {
 
-  override fun saveBook(bookId: String?, book: FinishedBookToSend) =
-      if (bookId == null) {
-        api.postFinishedBook(auth.getAccessToken(), book)
-      } else {
-        api.putFinishedBook(bookId, auth.getAccessToken(), book)
-      }
-
-  override fun saveBook(bookId: String?, book: PlannedBookToSend) =
-      if (bookId == null) {
-        api.postPlannedBook(auth.getAccessToken(), book)
-      } else {
-        api.putPlannedBook(bookId, auth.getAccessToken(), book)
-      }
+    override fun saveBook(bookId: String?, book: FinishedBookToSend) =
+        if (bookId == null) {
+            api.postFinishedBook(auth.getAccessToken(), book)
+        } else {
+            api.putFinishedBook(bookId, auth.getAccessToken(), book)
+        }
+
+    override fun saveBook(bookId: String?, book: PlannedBookToSend) =
+        if (bookId == null) {
+            api.postPlannedBook(auth.getAccessToken(), book)
+        } else {
+            api.putPlannedBook(bookId, auth.getAccessToken(), book)
+        }
 }
 
 class BookRepositoryMock : BookRepository {
 
-  override fun saveBook(bookId: String?, book: FinishedBookToSend): Completable =
-      Completable.fromAction { Thread.sleep(2000) }
+    override fun saveBook(bookId: String?, book: FinishedBookToSend): Completable =
+        Completable.fromAction { Thread.sleep(2000) }
 
-  override fun saveBook(bookId: String?, book: PlannedBookToSend): Completable =
-      Completable.fromAction { Thread.sleep(2000) }
+    override fun saveBook(bookId: String?, book: PlannedBookToSend): Completable =
+        Completable.fromAction { Thread.sleep(2000) }
 }

+ 7 - 7
app/src/main/java/me/vadik/knigopis/Configuration.kt

@@ -6,17 +6,17 @@ private const val PREFS_NAME = "knigopis-dev"
 private const val DEV_MODE_KEY = "dev-mode"
 
 interface Configuration {
-  fun isDevMode(): Boolean
-  fun setDevMode(enabled: Boolean)
+    fun isDevMode(): Boolean
+    fun setDevMode(enabled: Boolean)
 }
 
 class ConfigurationImpl(context: Context) : Configuration {
 
-  private val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+    private val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
 
-  override fun isDevMode() =
-      prefs.getBoolean(DEV_MODE_KEY, false)
+    override fun isDevMode() =
+        prefs.getBoolean(DEV_MODE_KEY, false)
 
-  override fun setDevMode(enabled: Boolean) =
-      prefs.edit().putBoolean(DEV_MODE_KEY, enabled).apply()
+    override fun setDevMode(enabled: Boolean) =
+        prefs.edit().putBoolean(DEV_MODE_KEY, enabled).apply()
 }

+ 22 - 22
app/src/main/java/me/vadik/knigopis/CoverPagerAdapter.kt

@@ -15,30 +15,30 @@ class CoverPagerAdapter(
     private val onFirstLoaded: () -> Unit
 ) : PagerAdapter() {
 
-  override fun instantiateItem(container: ViewGroup, position: Int): Any {
-    val context = container.context
-    val imageView = ImageView(context).apply {
-      layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)
-      setOnClickListener { onClick(position, position == urls.size - 1) }
-    }
-    Glide.with(context)
-        .load(urls[position])
-        .doOnSuccess {
-          if (position == 0) {
-            onFirstLoaded()
-          }
+    override fun instantiateItem(container: ViewGroup, position: Int): Any {
+        val context = container.context
+        val imageView = ImageView(context).apply {
+            layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)
+            setOnClickListener { onClick(position, position == urls.size - 1) }
         }
-        .apply(RequestOptions.centerCropTransform())
-        .into(imageView)
-    container.addView(imageView)
-    return imageView
-  }
+        Glide.with(context)
+            .load(urls[position])
+            .doOnSuccess {
+                if (position == 0) {
+                    onFirstLoaded()
+                }
+            }
+            .apply(RequestOptions.centerCropTransform())
+            .into(imageView)
+        container.addView(imageView)
+        return imageView
+    }
 
-  override fun destroyItem(container: ViewGroup, position: Int, obj: Any) {
-    container.removeView(obj as ImageView)
-  }
+    override fun destroyItem(container: ViewGroup, position: Int, obj: Any) {
+        container.removeView(obj as ImageView)
+    }
 
-  override fun isViewFromObject(view: View, obj: Any) = obj == view
+    override fun isViewFromObject(view: View, obj: Any) = obj == view
 
-  override fun getCount() = urls.size
+    override fun getCount() = urls.size
 }

+ 249 - 240
app/src/main/java/me/vadik/knigopis/MainActivity.kt

@@ -34,273 +34,282 @@ private const val VERSION_CLICK_COUNT_ON = 12
 
 class MainActivity : AppCompatActivity(), Router {
 
-  private val config by lazy { ConfigurationImpl(applicationContext) as Configuration }
-  private val api by lazy { app().baseApi.create(Endpoint::class.java) }
-  private val auth by lazy { KAuthImpl(applicationContext, api) as KAuth }
-  private val allBooks = mutableListOf<Book>()
-  private val booksAdapter by lazy {
-    BooksAdapter(BookCoverSearchImpl(
-        app().imageApi.create(ImageEndpoint::class.java),
-        BookCoverCacheImpl(applicationContext)
-    ), api, auth, this)
-  }
-  private val allBooksAdapter by lazy { booksAdapter.build(allBooks) }
-  private val navigation by lazy {
-    findView<BottomNavigationView>(R.id.navigation).apply {
-      visibility = if (config.isDevMode()) View.VISIBLE else View.GONE
+    private val config by lazy { ConfigurationImpl(applicationContext) as Configuration }
+    private val api by lazy { app().baseApi.create(Endpoint::class.java) }
+    private val auth by lazy { KAuthImpl(applicationContext, api) as KAuth }
+    private val allBooks = mutableListOf<Book>()
+    private val booksAdapter by lazy {
+        BooksAdapter(
+            BookCoverSearchImpl(
+                app().imageApi.create(ImageEndpoint::class.java),
+                BookCoverCacheImpl(applicationContext)
+            ), api, auth, this
+        )
     }
-  }
-  private var needUpdate = false
-  private lateinit var loginOption: MenuItem
-  private lateinit var currentTab: CurrentTab
-
-  override fun onCreate(savedInstanceState: Bundle?) {
-    if (config.isDevMode()) {
-      setTheme(R.style.DevTheme)
+    private val allBooksAdapter by lazy { booksAdapter.build(allBooks) }
+    private val navigation by lazy {
+        findView<BottomNavigationView>(R.id.navigation).apply {
+            visibility = if (config.isDevMode()) View.VISIBLE else View.GONE
+        }
     }
-    super.onCreate(savedInstanceState)
-    setContentView(R.layout.activity_main)
-    initRecyclerView(booksRecyclerView)
-    initNavigationView()
-    initToolbar(toolbar)
-    addBookButton.setOnClickListener {
-      startActivityForResult(createNewBookIntent(), BOOK_REQUEST_CODE)
+    private var needUpdate = false
+    private lateinit var loginOption: MenuItem
+    private lateinit var currentTab: CurrentTab
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        if (config.isDevMode()) {
+            setTheme(R.style.DevTheme)
+        }
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+        initRecyclerView(booksRecyclerView)
+        initNavigationView()
+        initToolbar(toolbar)
+        addBookButton.setOnClickListener {
+            startActivityForResult(createNewBookIntent(), BOOK_REQUEST_CODE)
+        }
     }
-  }
 
-  override fun onStart() {
-    super.onStart()
-    refreshOptionsMenu()
-    auth.requestAccessToken {
-      refreshOptionsMenu()
-      if (needUpdate) {
-        refresh()
-      }
+    override fun onStart() {
+        super.onStart()
+        refreshOptionsMenu()
+        auth.requestAccessToken {
+            refreshOptionsMenu()
+            if (needUpdate) {
+                refresh()
+            }
+        }
     }
-  }
 
-  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-    when (requestCode) {
-      ULOGIN_REQUEST_CODE -> {
-        if (resultCode == RESULT_OK && data != null) {
-          auth.saveTokenResponse(data)
-          needUpdate = true
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        when (requestCode) {
+            ULOGIN_REQUEST_CODE -> {
+                if (resultCode == RESULT_OK && data != null) {
+                    auth.saveTokenResponse(data)
+                    needUpdate = true
+                }
+            }
+            BOOK_REQUEST_CODE -> {
+                needUpdate = resultCode == RESULT_OK
+            }
         }
-      }
-      BOOK_REQUEST_CODE -> {
-        needUpdate = resultCode == RESULT_OK
-      }
     }
-  }
 
-  override fun openEditBookScreen(book: Book) {
-    startActivityForResult(createEditBookIntent(book.id, book is FinishedBook), BOOK_REQUEST_CODE)
-  }
+    override fun openEditBookScreen(book: Book) {
+        startActivityForResult(
+            createEditBookIntent(book.id, book is FinishedBook),
+            BOOK_REQUEST_CODE
+        )
+    }
 
-  private fun initNavigationView() {
-    refresh(HOME_TAB)
-    navigation.setOnNavigationItemSelectedListener { item ->
-      setCurrentTab(CurrentTab.getByItemId(item.itemId))
-      true
+    private fun initNavigationView() {
+        refresh(HOME_TAB)
+        navigation.setOnNavigationItemSelectedListener { item ->
+            setCurrentTab(CurrentTab.getByItemId(item.itemId))
+            true
+        }
     }
-  }
 
-  private fun initRecyclerView(recyclerView: RecyclerView): RecyclerView {
-    recyclerView.layoutManager = LinearLayoutManager(this)
-    return recyclerView
-  }
+    private fun initRecyclerView(recyclerView: RecyclerView): RecyclerView {
+        recyclerView.layoutManager = LinearLayoutManager(this)
+        return recyclerView
+    }
 
-  private fun initToolbar(toolbar: Toolbar) {
-    toolbar.inflateMenu(R.menu.options)
-    toolbar.setOnMenuItemClickListener { item ->
-      when (item.itemId) {
-        R.id.option_login -> {
-          login()
-          true
-        }
-        R.id.option_about -> {
-          val dialogView = View.inflate(this, R.layout.about, null)
-          val versionView = dialogView.aboutAppVersion
-          val designerView = dialogView.aboutDesignerText
-          versionView.text = BuildConfig.VERSION_NAME
-          var count = 0
-          val enabled = config.isDevMode()
-          val max = if (enabled) {
-            VERSION_CLICK_COUNT_OFF
-          } else {
-            VERSION_CLICK_COUNT_ON
-          }
-          if (enabled) {
-            designerView.setCompoundDrawablesRelativeWithIntrinsicBounds(
-                R.drawable.ic_about_designer_highlighted, 0, 0, 0
-            )
-          }
-          versionView.setOnClickListener {
-            if (++count == max) {
-              enabled.not().let {
-                if (it) toast(R.string.dev_mode_message)
-                config.setDevMode(it)
-                recreate()
-              }
+    private fun initToolbar(toolbar: Toolbar) {
+        toolbar.inflateMenu(R.menu.options)
+        toolbar.setOnMenuItemClickListener { item ->
+            when (item.itemId) {
+                R.id.option_login -> {
+                    login()
+                    true
+                }
+                R.id.option_about -> {
+                    val dialogView = View.inflate(this, R.layout.about, null)
+                    val versionView = dialogView.aboutAppVersion
+                    val designerView = dialogView.aboutDesignerText
+                    versionView.text = BuildConfig.VERSION_NAME
+                    var count = 0
+                    val enabled = config.isDevMode()
+                    val max = if (enabled) {
+                        VERSION_CLICK_COUNT_OFF
+                    } else {
+                        VERSION_CLICK_COUNT_ON
+                    }
+                    if (enabled) {
+                        designerView.setCompoundDrawablesRelativeWithIntrinsicBounds(
+                            R.drawable.ic_about_designer_highlighted, 0, 0, 0
+                        )
+                    }
+                    versionView.setOnClickListener {
+                        if (++count == max) {
+                            enabled.not().let {
+                                if (it) toast(R.string.dev_mode_message)
+                                config.setDevMode(it)
+                                recreate()
+                            }
+                        }
+                    }
+                    AlertDialog.Builder(this).setView(dialogView).show()
+                    true
+                }
+                R.id.option_refresh -> {
+                    refresh()
+                    true
+                }
+                else -> false
             }
-          }
-          AlertDialog.Builder(this).setView(dialogView).show()
-          true
         }
-        R.id.option_refresh -> {
-          refresh()
-          true
-        }
-        else -> false
-      }
+        loginOption = toolbar.menu.findItem(R.id.option_login)
     }
-    loginOption = toolbar.menu.findItem(R.id.option_login)
-  }
 
-  private fun login() {
-    RxPermissions(this).requestEach(READ_PHONE_STATE).subscribe({
-      when {
-        it.granted -> {
-          if (auth.isAuthorized()) {
-            auth.logout()
-            refresh()
-          } else {
-            startActivityForResult(auth.getTokenRequest(), ULOGIN_REQUEST_CODE)
-          }
-          refreshOptionsMenu()
-        }
-        it.shouldShowRequestPermissionRationale -> {
-          AlertDialog.Builder(this)
-              .setTitle(R.string.no_access)
-              .setMessage(R.string.no_access_message)
-              .setPositiveButton(R.string.no_access_retry_button) { _, _ ->
-                login()
-              }
-              .setNegativeButton(R.string.dialog_cancel_button, null)
-              .setCancelable(false)
-              .show()
-        }
-        else -> {
-          AlertDialog.Builder(this)
-              .setTitle(R.string.no_permissions)
-              .setMessage(R.string.no_permissions_message)
-              .setPositiveButton(R.string.no_permissions_goto_settings_button) { _, _ ->
-                startActivity(Intent(
-                    Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
-                    Uri.fromParts("package", packageName, null)
-                ))
-              }
-              .setNegativeButton(R.string.dialog_cancel_button, null)
-              .setCancelable(false)
-              .show()
-        }
-      }
-    }, {
-      logError("cannot request permission", it)
-    })
-  }
-
-  private fun refreshOptionsMenu() {
-    loginOption.isVisible = true
-    if (auth.isAuthorized()) {
-      loginOption.setTitle(R.string.option_logout)
-      loginOption.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
-    } else {
-      loginOption.setTitle(R.string.option_login)
-      loginOption.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
+    private fun login() {
+        RxPermissions(this).requestEach(READ_PHONE_STATE).subscribe({
+            when {
+                it.granted -> {
+                    if (auth.isAuthorized()) {
+                        auth.logout()
+                        refresh()
+                    } else {
+                        startActivityForResult(auth.getTokenRequest(), ULOGIN_REQUEST_CODE)
+                    }
+                    refreshOptionsMenu()
+                }
+                it.shouldShowRequestPermissionRationale -> {
+                    AlertDialog.Builder(this)
+                        .setTitle(R.string.no_access)
+                        .setMessage(R.string.no_access_message)
+                        .setPositiveButton(R.string.no_access_retry_button) { _, _ ->
+                            login()
+                        }
+                        .setNegativeButton(R.string.dialog_cancel_button, null)
+                        .setCancelable(false)
+                        .show()
+                }
+                else -> {
+                    AlertDialog.Builder(this)
+                        .setTitle(R.string.no_permissions)
+                        .setMessage(R.string.no_permissions_message)
+                        .setPositiveButton(R.string.no_permissions_goto_settings_button) { _, _ ->
+                            startActivity(
+                                Intent(
+                                    Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+                                    Uri.fromParts("package", packageName, null)
+                                )
+                            )
+                        }
+                        .setNegativeButton(R.string.dialog_cancel_button, null)
+                        .setCancelable(false)
+                        .show()
+                }
+            }
+        }, {
+            logError("cannot request permission", it)
+        })
     }
-  }
 
-  private fun refresh(tab: CurrentTab = currentTab) {
-    setCurrentTab(tab)
-    navigation.selectedItemId = tab.itemId
-  }
-
-  private fun setCurrentTab(tab: CurrentTab) {
-    needUpdate = false
-    addBookButton.hide()
-    currentTab = tab
-    when (tab) {
-      HOME_TAB -> refreshHomeTab()
-      USERS_TAB -> refreshUsersTab()
-      NOTES_TAB -> refreshNotesTab()
+    private fun refreshOptionsMenu() {
+        loginOption.isVisible = true
+        if (auth.isAuthorized()) {
+            loginOption.setTitle(R.string.option_logout)
+            loginOption.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
+        } else {
+            loginOption.setTitle(R.string.option_login)
+            loginOption.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
+        }
     }
-  }
 
-  private fun refreshHomeTab() {
-    if (booksProgressBar.alpha > 0) {
-      return
+    private fun refresh(tab: CurrentTab = currentTab) {
+        setCurrentTab(tab)
+        navigation.selectedItemId = tab.itemId
     }
-    booksRecyclerView.adapter = allBooksAdapter
-    allBooks.clear()
-    Singles.zip(
-        api.getPlannedBooks(auth.getAccessToken())
-            .map { it.sortedByDescending(PlannedBook::priority) },
-        api.getFinishedBooks(auth.getAccessToken())
-            .map { it.sortedByDescending(FinishedBook::order) }
-            .map { it.groupFinishedBooks() }
-    ).map { (planned, finished) ->
-      mutableListOf<Book>().apply {
-        if (planned.isNotEmpty()) {
-          add(BookHeader(getString(R.string.book_header_todo)))
-        }
-        addAll(planned)
-        addAll(finished)
-      }
-    }.io2main()
-        .doOnSubscribe {
-          booksProgressBar.show()
-          booksPlaceholder.hide()
+
+    private fun setCurrentTab(tab: CurrentTab) {
+        needUpdate = false
+        addBookButton.hide()
+        currentTab = tab
+        when (tab) {
+            HOME_TAB -> refreshHomeTab()
+            USERS_TAB -> refreshUsersTab()
+            NOTES_TAB -> refreshNotesTab()
         }
-        .doAfterTerminate {
-          booksProgressBar.hide()
+    }
+
+    private fun refreshHomeTab() {
+        if (booksProgressBar.alpha > 0) {
+            return
         }
-        .subscribe({ books ->
-          if (books.isEmpty()) {
-            booksPlaceholder.setText(R.string.error_no_books)
-            booksPlaceholder.show()
-          }
-          allBooks.addAll(books)
-          allBooksAdapter.notifyDataSetChanged()
-          addBookButton.show()
-        }, {
-          logError("cannot load books", it)
-          booksPlaceholder.setText(
-              if (it is HttpException && it.code() == 401) {
-                R.string.error_unauthorized
-              } else {
-                R.string.error_loading_books
-              }
-          )
-          booksPlaceholder.show()
-        })
-  }
+        booksRecyclerView.adapter = allBooksAdapter
+        allBooks.clear()
+        Singles.zip(
+            api.getPlannedBooks(auth.getAccessToken())
+                .map { it.sortedByDescending(PlannedBook::priority) },
+            api.getFinishedBooks(auth.getAccessToken())
+                .map { it.sortedByDescending(FinishedBook::order) }
+                .map { it.groupFinishedBooks() }
+        ).map { (planned, finished) ->
+            mutableListOf<Book>().apply {
+                if (planned.isNotEmpty()) {
+                    add(BookHeader(getString(R.string.book_header_todo)))
+                }
+                addAll(planned)
+                addAll(finished)
+            }
+        }.io2main()
+            .doOnSubscribe {
+                booksProgressBar.show()
+                booksPlaceholder.hide()
+            }
+            .doAfterTerminate {
+                booksProgressBar.hide()
+            }
+            .subscribe({ books ->
+                if (books.isEmpty()) {
+                    booksPlaceholder.setText(R.string.error_no_books)
+                    booksPlaceholder.show()
+                }
+                allBooks.addAll(books)
+                allBooksAdapter.notifyDataSetChanged()
+                addBookButton.show()
+            }, {
+                logError("cannot load books", it)
+                booksPlaceholder.setText(
+                    if (it is HttpException && it.code() == 401) {
+                        R.string.error_unauthorized
+                    } else {
+                        R.string.error_loading_books
+                    }
+                )
+                booksPlaceholder.show()
+            })
+    }
 
-  private fun refreshUsersTab() {
-    // todo
-  }
+    private fun refreshUsersTab() {
+        // todo
+    }
 
-  private fun refreshNotesTab() {
-    // todo
-  }
+    private fun refreshNotesTab() {
+        // todo
+    }
 
-  private fun List<FinishedBook>.groupFinishedBooks(): List<Book> {
-    val groupedBooks = mutableListOf<Book>()
-    var previousReadYear = Int.MAX_VALUE.toString()
-    forEachIndexed { index, book ->
-      val readYear = book.readYear
-      if (previousReadYear != readYear) {
-        groupedBooks.add(BookHeader(
-            when {
-              book.readYear.isEmpty() -> getString(R.string.book_header_done_other)
-              index == 0 -> getString(R.string.book_header_done_first, readYear)
-              else -> getString(R.string.book_header_done, readYear)
+    private fun List<FinishedBook>.groupFinishedBooks(): List<Book> {
+        val groupedBooks = mutableListOf<Book>()
+        var previousReadYear = Int.MAX_VALUE.toString()
+        forEachIndexed { index, book ->
+            val readYear = book.readYear
+            if (previousReadYear != readYear) {
+                groupedBooks.add(
+                    BookHeader(
+                        when {
+                            book.readYear.isEmpty() -> getString(R.string.book_header_done_other)
+                            index == 0 -> getString(R.string.book_header_done_first, readYear)
+                            else -> getString(R.string.book_header_done, readYear)
+                        }
+                    )
+                )
             }
-        ))
-      }
-      groupedBooks.add(book)
-      previousReadYear = book.readYear
+            groupedBooks.add(book)
+            previousReadYear = book.readYear
+        }
+        return groupedBooks
     }
-    return groupedBooks
-  }
 }

+ 1 - 1
app/src/main/java/me/vadik/knigopis/Router.kt

@@ -3,5 +3,5 @@ package me.vadik.knigopis
 import me.vadik.knigopis.model.Book
 
 interface Router {
-  fun openEditBookScreen(book: Book)
+    fun openEditBookScreen(book: Book)
 }

+ 31 - 31
app/src/main/java/me/vadik/knigopis/adapters/Adapter.kt

@@ -11,43 +11,43 @@ class Adapter<T>(
     private val layout: (T) -> Int
 ) {
 
-  val binders = mutableMapOf<@IdRes Int, (View, Int) -> Unit>()
-  @Suppress("MemberVisibilityCanPrivate")
-  val recyclerViewAdapter = object : RecyclerView.Adapter<ViewsHolder>() {
-    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
-        parent.inflate(viewType).let { rootView ->
-          ViewsHolder(rootView, binders.mapValues { (key, _) ->
-            rootView.findViewById<View>(key)
-          })
-        }
+    val binders = mutableMapOf<@IdRes Int, (View, Int) -> Unit>()
+    @Suppress("MemberVisibilityCanPrivate")
+    val recyclerViewAdapter = object : RecyclerView.Adapter<ViewsHolder>() {
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
+            parent.inflate(viewType).let { rootView ->
+                ViewsHolder(rootView, binders.mapValues { (key, _) ->
+                    rootView.findViewById<View>(key)
+                })
+            }
+
+        override fun onBindViewHolder(holder: ViewsHolder, position: Int) =
+            binders.forEach { (id, binder) ->
+                holder.views[id]?.let { view ->
+                    binder(view, position)
+                }
+            }
+
+        override fun getItemCount() = items.size
+
+        override fun getItemViewType(position: Int) = layout(items[position])
+    }
 
-    override fun onBindViewHolder(holder: ViewsHolder, position: Int) =
-        binders.forEach { (id, binder) ->
-          holder.views[id]?.let { view ->
-            binder(view, position)
-          }
+    inline fun <reified V : View> bind(@IdRes id: Int, crossinline binder: V.(Int) -> Unit): Adapter<T> {
+        binders[id] = { view, position ->
+            binder(view as V, position)
         }
-
-    override fun getItemCount() = items.size
-
-    override fun getItemViewType(position: Int) = layout(items[position])
-  }
-
-  inline fun <reified V : View> bind(@IdRes id: Int, crossinline binder: V.(Int) -> Unit): Adapter<T> {
-    binders[id] = { view, position ->
-      binder(view as V, position)
+        return this
     }
-    return this
-  }
 
-  inline fun <reified V : View> bind2(@IdRes id: Int, crossinline binder: V.(Int, RecyclerView.Adapter<ViewsHolder>) -> Unit): Adapter<T> {
-    binders[id] = { view, position ->
-      binder(view as V, position, recyclerViewAdapter)
+    inline fun <reified V : View> bind2(@IdRes id: Int, crossinline binder: V.(Int, RecyclerView.Adapter<ViewsHolder>) -> Unit): Adapter<T> {
+        binders[id] = { view, position ->
+            binder(view as V, position, recyclerViewAdapter)
+        }
+        return this
     }
-    return this
-  }
 
-  fun get() = recyclerViewAdapter
+    fun get() = recyclerViewAdapter
 }
 
 class ViewsHolder(rootView: View, val views: Map<Int, View>) : RecyclerView.ViewHolder(rootView)

+ 73 - 68
app/src/main/java/me/vadik/knigopis/adapters/BooksAdapter.kt

@@ -22,78 +22,83 @@ class BooksAdapter(
     private val router: Router
 ) {
 
-  fun build(books: MutableList<Book>) = Adapter(books) {
-    if (it is BookHeader) {
-      R.layout.header
-    } else {
-      R.layout.book
+    fun build(books: MutableList<Book>) = Adapter(books) {
+        if (it is BookHeader) {
+            R.layout.header
+        } else {
+            R.layout.book
+        }
     }
-  }
-      .bind2<View>(R.id.book_item_container) { bookIndex, adapter ->
-        val onDeleteConfirmed = { book: Book ->
-          val index = books.indexOfFirst { it.id == book.id }
-          if (index >= 0) {
-            when (book) {
-              is FinishedBook -> api.deleteFinishedBook(book.id, auth.getAccessToken())
-              is PlannedBook -> api.deletePlannedBook(book.id, auth.getAccessToken())
-              else -> throw UnsupportedOperationException()
+        .bind2<View>(R.id.book_item_container) { bookIndex, adapter ->
+            val onDeleteConfirmed = { book: Book ->
+                val index = books.indexOfFirst { it.id == book.id }
+                if (index >= 0) {
+                    when (book) {
+                        is FinishedBook -> api.deleteFinishedBook(book.id, auth.getAccessToken())
+                        is PlannedBook -> api.deletePlannedBook(book.id, auth.getAccessToken())
+                        else -> throw UnsupportedOperationException()
+                    }
+                        .io2main()
+                        .subscribe({}, {
+                            context.toast(R.string.cannot_delete_book)
+                            logError("cannot delete finished book", it)
+                        })
+                    books.removeAt(index)
+                    adapter.notifyItemRemoved(index)
+                }
+            }
+            val onDeleteClicked = { book: Book ->
+                AlertDialog.Builder(context)
+                    .setTitle(R.string.book_delete_confirmation_title)
+                    .setMessage(
+                        context.getString(
+                            R.string.book_delete_confirm_text,
+                            book.fullTitle
+                        )
+                    )
+                    .setNegativeButton(R.string.book_cancel_delete) { d, _ -> d.dismiss() }
+                    .setPositiveButton(R.string.book_confirm_delete) { d, _ ->
+                        onDeleteConfirmed(book)
+                        d.dismiss()
+                    }
+                    .show()
+            }
+            val book = books[bookIndex]
+            setOnLongClickListener {
+                AlertDialog.Builder(context)
+                    .setTitle(book.fullTitle)
+                    .setItems(R.array.book_context_menu) { dialog, menu ->
+                        when (menu) {
+                            0 -> router.openEditBookScreen(book)
+                            1 -> onDeleteClicked(book)
+                        }
+                        dialog.dismiss()
+                    }
+                    .show()
+                true
             }
-                .io2main()
-                .subscribe({}, {
-                  context.toast(R.string.cannot_delete_book)
-                  logError("cannot delete finished book", it)
+        }
+        .bind<ImageView>(R.id.book_image) {
+            hideNow()
+            coverSearch.search(books[it])
+                .subscribe({ coverUrl ->
+                    Glide.with(context)
+                        .load(coverUrl)
+                        .doOnSuccess { show() }
+                        .apply(RequestOptions.centerCropTransform())
+                        .into(this)
+                }, {
+                    logError("cannot load thumbnail", it)
                 })
-            books.removeAt(index)
-            adapter.notifyItemRemoved(index)
-          }
         }
-        val onDeleteClicked = { book: Book ->
-          AlertDialog.Builder(context)
-              .setTitle(R.string.book_delete_confirmation_title)
-              .setMessage(context.getString(R.string.book_delete_confirm_text, book.fullTitle))
-              .setNegativeButton(R.string.book_cancel_delete) { d, _ -> d.dismiss() }
-              .setPositiveButton(R.string.book_confirm_delete) { d, _ ->
-                onDeleteConfirmed(book)
-                d.dismiss()
-              }
-              .show()
+        .bind<TextView>(R.id.book_title) {
+            text = books[it].titleOrDefault
         }
-        val book = books[bookIndex]
-        setOnLongClickListener {
-          AlertDialog.Builder(context)
-              .setTitle(book.fullTitle)
-              .setItems(R.array.book_context_menu) { dialog, menu ->
-                when (menu) {
-                  0 -> router.openEditBookScreen(book)
-                  1 -> onDeleteClicked(book)
-                }
-                dialog.dismiss()
-              }
-              .show()
-          true
+        .bind<View>(R.id.header_divider) {
+            visibility = if (it == 0) View.INVISIBLE else View.VISIBLE
+        }
+        .bind<TextView>(R.id.book_author) {
+            text = books[it].authorOrDefault
         }
-      }
-      .bind<ImageView>(R.id.book_image) {
-        hideNow()
-        coverSearch.search(books[it])
-            .subscribe({ coverUrl ->
-              Glide.with(context)
-                  .load(coverUrl)
-                  .doOnSuccess { show() }
-                  .apply(RequestOptions.centerCropTransform())
-                  .into(this)
-            }, {
-              logError("cannot load thumbnail", it)
-            })
-      }
-      .bind<TextView>(R.id.book_title) {
-        text = books[it].titleOrDefault
-      }
-      .bind<View>(R.id.header_divider) {
-        visibility = if (it == 0) View.INVISIBLE else View.VISIBLE
-      }
-      .bind<TextView>(R.id.book_author) {
-        text = books[it].authorOrDefault
-      }
-      .get()
+        .get()
 }

+ 10 - 10
app/src/main/java/me/vadik/knigopis/adapters/UsersAdapter.kt

@@ -5,14 +5,14 @@ import me.vadik.knigopis.R
 import me.vadik.knigopis.model.User
 
 object UsersAdapter {
-  fun create(users: List<User>) = Adapter(users, { R.layout.user })
-      .bind<TextView>(R.id.user_name) {
-        text = users[it].nickname
-      }
-      .bind<TextView>(R.id.book_count) {
-        val user = users[it]
-        text = user.booksCount.toString()
-        setTextColor(user.color)
-      }
-      .get()
+    fun create(users: List<User>) = Adapter(users, { R.layout.user })
+        .bind<TextView>(R.id.user_name) {
+            text = users[it].nickname
+        }
+        .bind<TextView>(R.id.book_count) {
+            val user = users[it]
+            text = user.booksCount.toString()
+            setTextColor(user.color)
+        }
+        .get()
 }

+ 28 - 28
app/src/main/java/me/vadik/knigopis/api/BookCoverSearch.kt

@@ -11,8 +11,8 @@ private const val MAX_DELAY_IN_MICROSECONDS = 3000
 private const val MIN_TITLE_WORDS_COUNT = 2
 
 interface BookCoverSearch {
-  fun search(book: Book): Single<String>
-  fun search(query: String): Single<List<String>>
+    fun search(book: Book): Single<String>
+    fun search(query: String): Single<List<String>>
 }
 
 class BookCoverSearchImpl(
@@ -20,31 +20,31 @@ class BookCoverSearchImpl(
     private val bookCoverCache: BookCoverCache
 ) : BookCoverSearch {
 
-  override fun search(book: Book): Single<String> =
-      bookCoverCache.find(book.id).switchIfEmpty(
-          searchThumbnail(getSearchQuery(book))
-              .map { it.first() }
-              .map { thumbnailUrl ->
-                bookCoverCache.put(book.id, thumbnailUrl)
-                thumbnailUrl
-              }
-      ).io2main()
-
-  override fun search(query: String) =
-      searchThumbnail(query)
-          .io2main()
-
-  private fun searchThumbnail(query: String) =
-      imageEndpoint.searchImage(query)
-          .delay((Math.random() * MAX_DELAY_IN_MICROSECONDS).toLong(), TimeUnit.MICROSECONDS)
-          .map(ImageThumbnail::urls)
-
-  private fun getSearchQuery(book: Book) =
-      book.title.split(" ").size.let { titleWordsCount ->
-        if (titleWordsCount <= MIN_TITLE_WORDS_COUNT) {
-          "${book.title} ${book.author}"
-        } else {
-          book.title
+    override fun search(book: Book): Single<String> =
+        bookCoverCache.find(book.id).switchIfEmpty(
+            searchThumbnail(getSearchQuery(book))
+                .map { it.first() }
+                .map { thumbnailUrl ->
+                    bookCoverCache.put(book.id, thumbnailUrl)
+                    thumbnailUrl
+                }
+        ).io2main()
+
+    override fun search(query: String) =
+        searchThumbnail(query)
+            .io2main()
+
+    private fun searchThumbnail(query: String) =
+        imageEndpoint.searchImage(query)
+            .delay((Math.random() * MAX_DELAY_IN_MICROSECONDS).toLong(), TimeUnit.MICROSECONDS)
+            .map(ImageThumbnail::urls)
+
+    private fun getSearchQuery(book: Book) =
+        book.title.split(" ").size.let { titleWordsCount ->
+            if (titleWordsCount <= MIN_TITLE_WORDS_COUNT) {
+                "${book.title} ${book.author}"
+            } else {
+                book.title
+            }
         }
-      }
 }

+ 46 - 46
app/src/main/java/me/vadik/knigopis/api/Endpoint.kt

@@ -7,62 +7,62 @@ import retrofit2.http.*
 
 interface Endpoint {
 
-  @GET("user/get-credentials")
-  fun getCredentials(@Query("token") token: String): Single<Credentials>
+    @GET("user/get-credentials")
+    fun getCredentials(@Query("token") token: String): Single<Credentials>
 
-  @GET("books")
-  fun getFinishedBooks(@Query("access-token") accessToken: String): Single<List<FinishedBook>>
+    @GET("books")
+    fun getFinishedBooks(@Query("access-token") accessToken: String): Single<List<FinishedBook>>
 
-  @GET("books/{id}")
-  fun getFinishedBook(@Path("id") id: String): Single<FinishedBook>
+    @GET("books/{id}")
+    fun getFinishedBook(@Path("id") id: String): Single<FinishedBook>
 
-  @PUT("books/{id}")
-  fun putFinishedBook(
-      @Path("id") id: String,
-      @Query("access-token") accessToken: String,
-      @Body book: FinishedBookToSend
-  ): Completable
+    @PUT("books/{id}")
+    fun putFinishedBook(
+        @Path("id") id: String,
+        @Query("access-token") accessToken: String,
+        @Body book: FinishedBookToSend
+    ): Completable
 
-  @POST("books")
-  fun postFinishedBook(
-      @Query("access-token") accessToken: String,
-      @Body book: FinishedBookToSend
-  ): Completable
+    @POST("books")
+    fun postFinishedBook(
+        @Query("access-token") accessToken: String,
+        @Body book: FinishedBookToSend
+    ): Completable
 
-  @DELETE("books/{id}")
-  fun deleteFinishedBook(
-      @Path("id") id: String,
-      @Query("access-token") accessToken: String
-  ): Completable
+    @DELETE("books/{id}")
+    fun deleteFinishedBook(
+        @Path("id") id: String,
+        @Query("access-token") accessToken: String
+    ): Completable
 
-  @GET("wishes")
-  fun getPlannedBooks(@Query("access-token") accessToken: String): Single<List<PlannedBook>>
+    @GET("wishes")
+    fun getPlannedBooks(@Query("access-token") accessToken: String): Single<List<PlannedBook>>
 
-  @GET("wishes/{id}")
-  fun getPlannedBook(@Path("id") id: String): Single<PlannedBook>
+    @GET("wishes/{id}")
+    fun getPlannedBook(@Path("id") id: String): Single<PlannedBook>
 
-  @PUT("wishes/{id}")
-  fun putPlannedBook(
-      @Path("id") id: String,
-      @Query("access-token") accessToken: String,
-      @Body book: PlannedBookToSend
-  ): Completable
+    @PUT("wishes/{id}")
+    fun putPlannedBook(
+        @Path("id") id: String,
+        @Query("access-token") accessToken: String,
+        @Body book: PlannedBookToSend
+    ): Completable
 
-  @POST("wishes")
-  fun postPlannedBook(
-      @Query("access-token") accessToken: String,
-      @Body book: PlannedBookToSend
-  ): Completable
+    @POST("wishes")
+    fun postPlannedBook(
+        @Query("access-token") accessToken: String,
+        @Body book: PlannedBookToSend
+    ): Completable
 
-  @DELETE("wishes/{id}")
-  fun deletePlannedBook(
-      @Path("id") id: String,
-      @Query("access-token") accessToken: String
-  ): Completable
+    @DELETE("wishes/{id}")
+    fun deletePlannedBook(
+        @Path("id") id: String,
+        @Query("access-token") accessToken: String
+    ): Completable
 
-  @GET("users/latest")
-  fun getLatestUsers(): Single<Map<String, User>>
+    @GET("users/latest")
+    fun getLatestUsers(): Single<Map<String, User>>
 
-  @GET("books/latest-notes")
-  fun getLatestBooksWithNotes(): Single<Map<String, FinishedBook>>
+    @GET("books/latest-notes")
+    fun getLatestBooksWithNotes(): Single<Map<String, FinishedBook>>
 }

+ 5 - 5
app/src/main/java/me/vadik/knigopis/api/ImageEndpoint.kt

@@ -7,9 +7,9 @@ import retrofit2.http.Query
 
 interface ImageEndpoint {
 
-  @GET("search/images")
-  fun searchImage(
-      @Query("q") query: String,
-      @Query("count") count: Int = 10
-  ): Single<ImageThumbnail>
+    @GET("search/images")
+    fun searchImage(
+        @Query("q") query: String,
+        @Query("count") count: Int = 10
+    ): Single<ImageThumbnail>
 }

+ 12 - 8
app/src/main/java/me/vadik/knigopis/api/gson/ImageThumbnailDeserializer.kt

@@ -7,12 +7,16 @@ import me.vadik.knigopis.model.ImageThumbnail
 import java.lang.reflect.Type
 
 class ImageThumbnailDeserializer : JsonDeserializer<ImageThumbnail> {
-  override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext) =
-      json.asJsonObject
-          .getAsJsonObject("data")
-          .getAsJsonObject("result")
-          .getAsJsonArray("items")
-          .map { it.asJsonObject["thumbnail"].asString }
-          .map { "https:" + it }
-          .let(::ImageThumbnail)
+    override fun deserialize(
+        json: JsonElement,
+        typeOfT: Type,
+        context: JsonDeserializationContext
+    ) =
+        json.asJsonObject
+            .getAsJsonObject("data")
+            .getAsJsonObject("result")
+            .getAsJsonArray("items")
+            .map { it.asJsonObject["thumbnail"].asString }
+            .map { "https:" + it }
+            .let(::ImageThumbnail)
 }

+ 37 - 37
app/src/main/java/me/vadik/knigopis/auth/KAuth.kt

@@ -13,12 +13,12 @@ private const val TOKEN_KEY = "token"
 private const val ACCESS_TOKEN_KEY = "access_token"
 
 interface KAuth {
-  fun isAuthorized(): Boolean
-  fun getAccessToken(): String
-  fun getTokenRequest(): Intent
-  fun saveTokenResponse(data: Intent)
-  fun requestAccessToken(onSuccess: () -> Unit)
-  fun logout()
+    fun isAuthorized(): Boolean
+    fun getAccessToken(): String
+    fun getTokenRequest(): Intent
+    fun saveTokenResponse(data: Intent)
+    fun requestAccessToken(onSuccess: () -> Unit)
+    fun logout()
 }
 
 class KAuthImpl(
@@ -26,37 +26,37 @@ class KAuthImpl(
     private val api: Endpoint
 ) : KAuth {
 
-  private val preferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
-
-  override fun isAuthorized() = preferences.contains(ACCESS_TOKEN_KEY)
-
-  override fun getAccessToken(): String = preferences.getString(ACCESS_TOKEN_KEY, "")
-
-  override fun getTokenRequest(): Intent {
-    return Intent(context, UloginAuthActivity::class.java)
-        .putExtra(UloginAuthActivity.FIELDS, arrayOf(TOKEN_KEY))
-  }
-
-  override fun saveTokenResponse(data: Intent) {
-    val userData = data.getSerializableExtra(UloginAuthActivity.USERDATA) as HashMap<*, *>
-    preferences.edit().putString(TOKEN_KEY, userData[TOKEN_KEY].toString()).apply()
-  }
-
-  override fun requestAccessToken(onSuccess: () -> Unit) {
-    val token = preferences.getString(TOKEN_KEY, null)
-    if (token != null && !isAuthorized()) {
-      api.getCredentials(token)
-          .io2main()
-          .subscribe({
-            preferences.edit().putString(ACCESS_TOKEN_KEY, it.accessToken).apply()
-            onSuccess()
-          }, {
-            logError("cannot get credentials", it)
-          })
+    private val preferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+
+    override fun isAuthorized() = preferences.contains(ACCESS_TOKEN_KEY)
+
+    override fun getAccessToken(): String = preferences.getString(ACCESS_TOKEN_KEY, "")
+
+    override fun getTokenRequest(): Intent {
+        return Intent(context, UloginAuthActivity::class.java)
+            .putExtra(UloginAuthActivity.FIELDS, arrayOf(TOKEN_KEY))
+    }
+
+    override fun saveTokenResponse(data: Intent) {
+        val userData = data.getSerializableExtra(UloginAuthActivity.USERDATA) as HashMap<*, *>
+        preferences.edit().putString(TOKEN_KEY, userData[TOKEN_KEY].toString()).apply()
+    }
+
+    override fun requestAccessToken(onSuccess: () -> Unit) {
+        val token = preferences.getString(TOKEN_KEY, null)
+        if (token != null && !isAuthorized()) {
+            api.getCredentials(token)
+                .io2main()
+                .subscribe({
+                    preferences.edit().putString(ACCESS_TOKEN_KEY, it.accessToken).apply()
+                    onSuccess()
+                }, {
+                    logError("cannot get credentials", it)
+                })
+        }
     }
-  }
 
-  override fun logout() {
-    preferences.edit().remove(TOKEN_KEY).remove(ACCESS_TOKEN_KEY).apply()
-  }
+    override fun logout() {
+        preferences.edit().remove(TOKEN_KEY).remove(ACCESS_TOKEN_KEY).apply()
+    }
 }

+ 33 - 32
app/src/main/java/me/vadik/knigopis/extensions.kt

@@ -25,7 +25,8 @@ private const val TAG = "Knigopis"
 
 fun Context.toast(message: String) = Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
 
-fun Context.toast(@StringRes messageId: Int) = Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show()
+fun Context.toast(@StringRes messageId: Int) =
+    Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show()
 
 fun Activity.app() = application as App
 
@@ -48,50 +49,50 @@ fun String.orDefault(default: String) = if (isEmpty()) default else this
 
 fun <T> RequestBuilder<T>.doOnSuccess(onSuccess: () -> Unit): RequestBuilder<T> =
     listener(object : RequestListener<T> {
-      override fun onResourceReady(
-          resource: T?,
-          model: Any?,
-          target: Target<T>?,
-          dataSource: DataSource?,
-          isFirstResource: Boolean
-      ): Boolean {
-        onSuccess()
-        return false
-      }
-
-      override fun onLoadFailed(
-          e: GlideException?,
-          model: Any?,
-          target: Target<T>?,
-          isFirstResource: Boolean
-      ): Boolean {
-        return false
-      }
+        override fun onResourceReady(
+            resource: T?,
+            model: Any?,
+            target: Target<T>?,
+            dataSource: DataSource?,
+            isFirstResource: Boolean
+        ): Boolean {
+            onSuccess()
+            return false
+        }
+
+        override fun onLoadFailed(
+            e: GlideException?,
+            model: Any?,
+            target: Target<T>?,
+            isFirstResource: Boolean
+        ): Boolean {
+            return false
+        }
     })
 
 fun View.showNow() {
-  alpha = 1f
+    alpha = 1f
 }
 
 fun View.hideNow() {
-  alpha = 0f
+    alpha = 0f
 }
 
 fun View.show() {
-  animate().alpha(1f).setDuration(200)
-      .withStartAction { visibility = View.VISIBLE }
-      .start()
+    animate().alpha(1f).setDuration(200)
+        .withStartAction { visibility = View.VISIBLE }
+        .start()
 }
 
 fun View.hide() {
-  animate().alpha(0f).setDuration(200)
-      .withEndAction { visibility = View.GONE }
-      .start()
+    animate().alpha(0f).setDuration(200)
+        .withEndAction { visibility = View.GONE }
+        .start()
 }
 
 fun Activity.hideKeyboard() {
-  currentFocus?.let { view ->
-    (getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
-        .hideSoftInputFromWindow(view.windowToken, 0)
-  }
+    currentFocus?.let { view ->
+        (getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
+            .hideSoftInputFromWindow(view.windowToken, 0)
+    }
 }

+ 6 - 6
app/src/main/java/me/vadik/knigopis/model/Book.kt

@@ -3,10 +3,10 @@ package me.vadik.knigopis.model
 import me.vadik.knigopis.orDefault
 
 interface Book {
-  val id: String
-  val title: String
-  val author: String
-  val titleOrDefault get() = title.orDefault("(без названия)")
-  val authorOrDefault get() = author.orDefault("(автор не указан)")
-  val fullTitle get() = titleOrDefault + " — " + authorOrDefault
+    val id: String
+    val title: String
+    val author: String
+    val titleOrDefault get() = title.orDefault("(без названия)")
+    val authorOrDefault get() = author.orDefault("(автор не указан)")
+    val fullTitle get() = titleOrDefault + " — " + authorOrDefault
 }

+ 12 - 12
app/src/main/java/me/vadik/knigopis/model/Credentials.kt

@@ -8,16 +8,16 @@ class Credentials(
     val userFull: UserFull
 ) {
 
-  class UserFull(
-      val id: String,
-      val lang: String,
-      val nickname: String,
-      val photo: String,
-      val profile: String,
-      val identity: String,
-      val booksCount: Int,
-      val subscriptions: Map<String, Int>,
-      val createdAt: String,
-      val updatedAt: String
-  )
+    class UserFull(
+        val id: String,
+        val lang: String,
+        val nickname: String,
+        val photo: String,
+        val profile: String,
+        val identity: String,
+        val booksCount: Int,
+        val subscriptions: Map<String, Int>,
+        val createdAt: String,
+        val updatedAt: String
+    )
 }

+ 7 - 7
app/src/main/java/me/vadik/knigopis/model/CurrentTab.kt

@@ -5,12 +5,12 @@ import me.vadik.knigopis.R
 
 enum class CurrentTab(@IdRes val itemId: Int) {
 
-  HOME_TAB(R.id.navigation_home),
-  USERS_TAB(R.id.navigation_users),
-  NOTES_TAB(R.id.navigation_notes);
+    HOME_TAB(R.id.navigation_home),
+    USERS_TAB(R.id.navigation_users),
+    NOTES_TAB(R.id.navigation_notes);
 
-  companion object {
-    fun getByItemId(@IdRes itemId: Int) =
-        checkNotNull(values().find { it.itemId == itemId })
-  }
+    companion object {
+        fun getByItemId(@IdRes itemId: Int) =
+            checkNotNull(values().find { it.itemId == itemId })
+    }
 }

+ 3 - 3
app/src/main/java/me/vadik/knigopis/model/FinishedBook.kt

@@ -11,7 +11,7 @@ class FinishedBook(
     val createdAt: String,
     val user: User
 ) : Book {
-  val order
-    get() = arrayOf(readYear, readMonth, readDay)
-        .joinToString("") { it.padStart(4, '0') }
+    val order
+        get() = arrayOf(readYear, readMonth, readDay)
+            .joinToString("") { it.padStart(4, '0') }
 }

+ 5 - 5
app/src/main/java/me/vadik/knigopis/model/User.kt

@@ -7,9 +7,9 @@ class User(
     val updatedAt: String
 ) {
 
-  val color: Int
-    get() {
-      val alpha = Math.min(0xff, booksCount)
-      return alpha shl 24
-    }
+    val color: Int
+        get() {
+            val alpha = Math.min(0xff, booksCount)
+            return alpha shl 24
+        }
 }