Browse Source

Added login activity with custom tabs

Vadik Sirekanyan 6 years ago
parent
commit
7b2e3d0315
25 changed files with 545 additions and 61 deletions
  1. 1 0
      app/build.gradle.kts
  2. 16 0
      app/src/main/AndroidManifest.xml
  3. 3 0
      app/src/main/java/com/sirekanyan/knigopis/common/extensions/String.kt
  4. 17 0
      app/src/main/java/com/sirekanyan/knigopis/dependency/login.kt
  5. 1 5
      app/src/main/java/com/sirekanyan/knigopis/dependency/main.kt
  6. 11 25
      app/src/main/java/com/sirekanyan/knigopis/feature/MainActivity.kt
  7. 3 31
      app/src/main/java/com/sirekanyan/knigopis/feature/MainPresenter.kt
  8. 115 0
      app/src/main/java/com/sirekanyan/knigopis/feature/login/LoginActivity.kt
  9. 67 0
      app/src/main/java/com/sirekanyan/knigopis/feature/login/LoginPresenter.kt
  10. 77 0
      app/src/main/java/com/sirekanyan/knigopis/feature/login/LoginView.kt
  11. 17 0
      app/src/main/java/com/sirekanyan/knigopis/feature/login/Website.kt
  12. 47 0
      app/src/main/java/com/sirekanyan/knigopis/feature/login/constants.kt
  13. 9 0
      app/src/main/res/drawable/ic_login_fb.xml
  14. 9 0
      app/src/main/res/drawable/ic_login_go.xml
  15. 6 0
      app/src/main/res/drawable/ic_login_in.xml
  16. 9 0
      app/src/main/res/drawable/ic_login_mr.xml
  17. 9 0
      app/src/main/res/drawable/ic_login_tw.xml
  18. 9 0
      app/src/main/res/drawable/ic_login_vk.xml
  19. 9 0
      app/src/main/res/drawable/ic_login_ya.xml
  20. 24 0
      app/src/main/res/layout/login_activity.xml
  21. 35 0
      app/src/main/res/layout/website_layout.xml
  22. 10 0
      app/src/main/res/menu/login_options.xml
  23. 16 0
      app/src/main/res/values-ru/strings.xml
  24. 9 0
      app/src/main/res/values/colors.xml
  25. 16 0
      app/src/main/res/values/strings.xml

+ 1 - 0
app/build.gradle.kts

@@ -40,6 +40,7 @@ dependencies {
     implementation("com.android.support:design:28.0.0")
     implementation("com.android.support:support-vector-drawable:28.0.0")
     implementation("com.android.support.constraint:constraint-layout:1.1.3")
+    implementation("com.android.support:customtabs:28.0.0")
 
     // rxjava
     implementation("io.reactivex.rxjava2:rxjava:2.2.5")

+ 16 - 0
app/src/main/AndroidManifest.xml

@@ -29,8 +29,10 @@
             </intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
+
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.BROWSABLE" />
+
                 <data
                     android:host="www.knigopis.com"
                     android:scheme="http" />
@@ -52,6 +54,20 @@
             android:name="ru.ulogin.sdk.UloginAuthActivity"
             android:configChanges="orientation|screenSize" />
 
+        <activity android:name=".feature.login.LoginActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data
+                    android:host="532b8e7fc54c52b6df5b55181acc241a"
+                    android:path="/8e89f82ac2a6a7972452eae93e3bb734"
+                    android:scheme="e270636c0efc6cad95130113d3bbafc3" />
+            </intent-filter>
+        </activity>
+
     </application>
 
 </manifest>

+ 3 - 0
app/src/main/java/com/sirekanyan/knigopis/common/extensions/String.kt

@@ -1,6 +1,9 @@
 package com.sirekanyan.knigopis.common.extensions
 
 import android.net.Uri
+import kotlin.random.Random
+
+val RANDOM_ID = Array(16) { Random.nextInt(0, 16).toString(16) }.joinToString("")
 
 private val HTTP_SCHEMES = setOf("http", "https")
 

+ 17 - 0
app/src/main/java/com/sirekanyan/knigopis/dependency/login.kt

@@ -0,0 +1,17 @@
+package com.sirekanyan.knigopis.dependency
+
+import com.sirekanyan.knigopis.common.android.permissions.PermissionsImpl
+import com.sirekanyan.knigopis.common.extensions.app
+import com.sirekanyan.knigopis.common.extensions.getRootView
+import com.sirekanyan.knigopis.feature.login.LoginActivity
+import com.sirekanyan.knigopis.feature.login.LoginPresenter
+import com.sirekanyan.knigopis.feature.login.LoginPresenterImpl
+import com.sirekanyan.knigopis.feature.login.LoginViewImpl
+
+fun LoginActivity.providePresenter(): LoginPresenter {
+    val permissions = PermissionsImpl(this, app.config)
+    return LoginPresenterImpl(this, permissions).also { presenter ->
+        presenter.view = LoginViewImpl(getRootView(), presenter)
+        permissions.callback = presenter
+    }
+}

+ 1 - 5
app/src/main/java/com/sirekanyan/knigopis/dependency/main.kt

@@ -1,7 +1,6 @@
 package com.sirekanyan.knigopis.dependency
 
 import com.sirekanyan.knigopis.common.android.dialog.DialogFactory
-import com.sirekanyan.knigopis.common.android.permissions.PermissionsImpl
 import com.sirekanyan.knigopis.common.extensions.app
 import com.sirekanyan.knigopis.common.extensions.getRootView
 import com.sirekanyan.knigopis.feature.*
@@ -21,7 +20,6 @@ fun MainActivity.providePresenter(): MainPresenter {
     val booksPresenter = BooksPresenterImpl(this, app.bookRepository)
     val usersPresenter = UsersPresenterImpl(this, app.userRepository, app.resourceProvider)
     val notesPresenter = NotesPresenterImpl(this, app.noteRepository)
-    val permissions = PermissionsImpl(this, app.config)
     return MainPresenterImpl(
         mapOf(
             BOOKS_TAB to booksPresenter,
@@ -30,8 +28,7 @@ fun MainActivity.providePresenter(): MainPresenter {
         ),
         this,
         app.config,
-        app.authRepository,
-        permissions
+        app.authRepository
     ).also { mainPresenter ->
         val rootView = getRootView()
         val progressView = ProgressViewImpl(rootView.swipeRefresh, mainPresenter)
@@ -49,6 +46,5 @@ fun MainActivity.providePresenter(): MainPresenter {
             p.parent = mainPresenter
         }
         mainPresenter.view = MainViewImpl(rootView, mainPresenter)
-        permissions.callback = mainPresenter
     }
 }

+ 11 - 25
app/src/main/java/com/sirekanyan/knigopis/feature/MainActivity.kt

@@ -1,19 +1,19 @@
 package com.sirekanyan.knigopis.feature
 
+import android.content.Context
 import android.content.Intent
-import android.content.Intent.ACTION_VIEW
+import android.content.Intent.*
 import android.net.Uri
 import android.os.Bundle
 import com.sirekanyan.knigopis.R
 import com.sirekanyan.knigopis.common.BaseActivity
-import com.sirekanyan.knigopis.common.android.permissions.PermissionResult
 import com.sirekanyan.knigopis.common.extensions.*
-import com.sirekanyan.knigopis.common.functions.createLoginIntent
 import com.sirekanyan.knigopis.common.functions.extra
 import com.sirekanyan.knigopis.common.functions.logError
 import com.sirekanyan.knigopis.dependency.providePresenter
 import com.sirekanyan.knigopis.feature.book.createBookIntent
 import com.sirekanyan.knigopis.feature.books.BooksPresenter
+import com.sirekanyan.knigopis.feature.login.startLoginActivity
 import com.sirekanyan.knigopis.feature.notes.NotesPresenter
 import com.sirekanyan.knigopis.feature.profile.createProfileIntent
 import com.sirekanyan.knigopis.feature.user.createUserIntent
@@ -21,12 +21,17 @@ import com.sirekanyan.knigopis.feature.users.UsersPresenter
 import com.sirekanyan.knigopis.feature.users.getMainState
 import com.sirekanyan.knigopis.feature.users.saveMainState
 import com.sirekanyan.knigopis.model.*
-import ru.ulogin.sdk.UloginAuthActivity
 
-private const val LOGIN_REQUEST_CODE = 0
 private const val BOOK_REQUEST_CODE = 1
 private val CURRENT_TAB_EXTRA = extra("current_tab")
 
+fun Context.startMainActivity() {
+    startActivity(
+        Intent(this, MainActivity::class.java)
+            .setFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)
+    )
+}
+
 class MainActivity : BaseActivity(),
     MainPresenter.Router,
     BooksPresenter.Router,
@@ -82,13 +87,6 @@ class MainActivity : BaseActivity(),
 
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
         when (requestCode) {
-            LOGIN_REQUEST_CODE -> {
-                if (resultCode == RESULT_OK && data != null) {
-                    val userData = data.getSerializableExtra(UloginAuthActivity.USERDATA)
-                    val token = (userData as HashMap<*, *>)["token"].toString()
-                    presenter.onLoginScreenResult(token)
-                }
-            }
             BOOK_REQUEST_CODE -> {
                 if (resultCode == RESULT_OK) {
                     presenter.onBookScreenResult()
@@ -97,18 +95,6 @@ class MainActivity : BaseActivity(),
         }
     }
 
-    override fun onRequestPermissionsResult(
-        requestCode: Int,
-        permissions: Array<String>,
-        results: IntArray
-    ) {
-        if (permissions.size == 1 && results.size == 1) {
-            PermissionResult.create(requestCode, permissions.single(), results.single())?.let {
-                presenter.onPermissionResult(it)
-            }
-        }
-    }
-
     override fun onBackPressed() {
         if (!presenter.back()) {
             super.onBackPressed()
@@ -116,7 +102,7 @@ class MainActivity : BaseActivity(),
     }
 
     override fun openLoginScreen() {
-        startActivityForResult(createLoginIntent(), LOGIN_REQUEST_CODE)
+        startLoginActivity()
     }
 
     override fun openProfileScreen() {

+ 3 - 31
app/src/main/java/com/sirekanyan/knigopis/feature/MainPresenter.kt

@@ -2,9 +2,6 @@ package com.sirekanyan.knigopis.feature
 
 import com.sirekanyan.knigopis.common.BasePresenter
 import com.sirekanyan.knigopis.common.Presenter
-import com.sirekanyan.knigopis.common.android.permissions.Permission
-import com.sirekanyan.knigopis.common.android.permissions.PermissionResult
-import com.sirekanyan.knigopis.common.android.permissions.Permissions
 import com.sirekanyan.knigopis.common.functions.logError
 import com.sirekanyan.knigopis.feature.users.MainPresenterState
 import com.sirekanyan.knigopis.model.CurrentTab
@@ -20,9 +17,7 @@ interface MainPresenter : Presenter {
     fun start()
     fun resume()
     fun back(): Boolean
-    fun onLoginScreenResult(token: String)
     fun onBookScreenResult()
-    fun onPermissionResult(permissionResult: PermissionResult)
 
     interface Router {
         fun openLoginScreen()
@@ -36,19 +31,16 @@ class MainPresenterImpl(
     private val pagePresenters: Map<CurrentTab, PagePresenter>,
     private val router: MainPresenter.Router,
     private val config: Configuration,
-    private val auth: AuthRepository,
-    private val permissions: Permissions
+    private val auth: AuthRepository
 ) : BasePresenter<MainView>(*pagePresenters.values.toTypedArray()),
     MainPresenter,
     MainView.Callbacks,
     PagesPresenter,
-    ProgressView.Callbacks,
-    Permissions.Callback {
+    ProgressView.Callbacks {
 
     private val loadedTabs = mutableSetOf<CurrentTab>()
     private var currentTab: CurrentTab? = null
     private var booksChanged = false
-    private var userLoggedIn = false
 
     override val state
         get() = currentTab?.let { MainPresenterState(it) }
@@ -67,11 +59,6 @@ class MainPresenterImpl(
     override fun resume() {
         auth.authorize().bind({
             refreshButtons()
-            if (userLoggedIn) {
-                userLoggedIn = false
-                currentTab = BOOKS_TAB
-                refresh()
-            }
         }, {
             logError("cannot check credentials", it)
         })
@@ -116,11 +103,6 @@ class MainPresenterImpl(
         }
     }
 
-    override fun onLoginScreenResult(token: String) {
-        auth.saveToken(token)
-        userLoggedIn = true
-    }
-
     override fun onBookScreenResult() {
         booksChanged = true
     }
@@ -140,17 +122,7 @@ class MainPresenterImpl(
     }
 
     override fun onLoginOptionClicked() {
-        permissions.requestPermission(Permission.PHONE)
-    }
-
-    override fun onPermissionResult(permissionResult: PermissionResult) {
-        permissions.submitResult(permissionResult)
-    }
-
-    override fun onGranted(permission: Permission) {
-        if (permission == Permission.PHONE) {
-            router.openLoginScreen()
-        }
+        router.openLoginScreen()
     }
 
     override fun onProfileOptionClicked() {

+ 115 - 0
app/src/main/java/com/sirekanyan/knigopis/feature/login/LoginActivity.kt

@@ -0,0 +1,115 @@
+package com.sirekanyan.knigopis.feature.login
+
+import android.content.ActivityNotFoundException
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.ACTION_VIEW
+import android.content.Intent.FLAG_ACTIVITY_NO_HISTORY
+import android.net.Uri
+import android.os.Bundle
+import android.support.customtabs.CustomTabsIntent
+import android.support.v4.content.ContextCompat
+import android.support.v7.app.AppCompatActivity
+import com.sirekanyan.knigopis.R
+import com.sirekanyan.knigopis.common.android.permissions.PermissionResult
+import com.sirekanyan.knigopis.common.extensions.app
+import com.sirekanyan.knigopis.common.extensions.setDarkTheme
+import com.sirekanyan.knigopis.common.functions.createLoginIntent
+import com.sirekanyan.knigopis.dependency.providePresenter
+import com.sirekanyan.knigopis.feature.startMainActivity
+import ru.ulogin.sdk.UloginAuthActivity
+
+private const val MARKET_URI = "market://details?id="
+private const val GOOGLE_PLAY_URI = "https://play.google.com/store/apps/details?id="
+private const val LOGIN_REQUEST_CODE = 0
+
+fun Context.startLoginActivity() {
+    startActivity(Intent(this, LoginActivity::class.java))
+}
+
+class LoginActivity : AppCompatActivity(), LoginPresenter.Router {
+
+    private val presenter by lazy { providePresenter() }
+    private val auth by lazy { app.authRepository }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        setDarkTheme(app.config.isDarkTheme)
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.login_activity)
+        presenter.init()
+    }
+
+    override fun onResume() {
+        super.onResume()
+        intent?.data?.findParameter("token")?.let { token ->
+            intent = null
+            auth.saveToken(token)
+            startMainActivity()
+        }
+    }
+
+    override fun onRequestPermissionsResult(
+        requestCode: Int,
+        permissions: Array<String>,
+        results: IntArray
+    ) {
+        if (permissions.size == 1 && results.size == 1) {
+            PermissionResult.create(requestCode, permissions.single(), results.single())?.let {
+                presenter.onPermissionResult(it)
+            }
+        }
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        when (requestCode) {
+            LOGIN_REQUEST_CODE -> {
+                if (resultCode == RESULT_OK && data != null) {
+                    val userData = data.getSerializableExtra(UloginAuthActivity.USERDATA)
+                    val token = (userData as HashMap<*, *>)["token"].toString()
+                    auth.saveToken(token)
+                    startMainActivity()
+                }
+            }
+        }
+    }
+
+    override fun openBrowser(website: Website): Boolean {
+        val toolbarColor = ContextCompat.getColor(this, website.color)
+        val customTabsIntent = CustomTabsIntent.Builder().setToolbarColor(toolbarColor).build()
+        customTabsIntent.intent.addFlags(FLAG_ACTIVITY_NO_HISTORY)
+        return try {
+            customTabsIntent.launchUrl(this, website.uri)
+            true
+        } catch (ex: ActivityNotFoundException) {
+            false
+        }
+    }
+
+    override fun openMarket(packageName: String) {
+        try {
+            startActivity(Intent(ACTION_VIEW, Uri.parse(MARKET_URI + packageName)))
+        } catch (ex: ActivityNotFoundException) {
+            startActivity(Intent(ACTION_VIEW, Uri.parse(GOOGLE_PLAY_URI + packageName)))
+        }
+    }
+
+    override fun openLegacyLoginScreen() {
+        startActivityForResult(createLoginIntent(), LOGIN_REQUEST_CODE)
+    }
+
+    override fun close() {
+        finish()
+    }
+
+    private fun Uri.findParameter(key: String): String? =
+        if (
+            scheme == LOGIN_CALLBACK_URI.scheme
+            && host == LOGIN_CALLBACK_URI.host
+            && path == LOGIN_CALLBACK_URI.path
+        ) {
+            getQueryParameter(key)
+        } else {
+            null
+        }
+
+}

+ 67 - 0
app/src/main/java/com/sirekanyan/knigopis/feature/login/LoginPresenter.kt

@@ -0,0 +1,67 @@
+package com.sirekanyan.knigopis.feature.login
+
+import com.sirekanyan.knigopis.common.BasePresenter
+import com.sirekanyan.knigopis.common.Presenter
+import com.sirekanyan.knigopis.common.android.permissions.Permission
+import com.sirekanyan.knigopis.common.android.permissions.PermissionResult
+import com.sirekanyan.knigopis.common.android.permissions.Permissions
+
+interface LoginPresenter : Presenter {
+
+    fun init()
+    fun onPermissionResult(permissionResult: PermissionResult)
+
+    interface Router {
+
+        fun openBrowser(website: Website): Boolean
+        fun openMarket(packageName: String)
+        fun openLegacyLoginScreen()
+        fun close()
+
+    }
+
+}
+
+class LoginPresenterImpl(
+    private val router: LoginPresenter.Router,
+    private val permissions: Permissions
+) : BasePresenter<LoginView>(),
+    LoginPresenter,
+    LoginView.Callbacks,
+    Permissions.Callback {
+
+    override fun init() {
+        Website.values().forEach { website ->
+            view.addWebsite(website)
+        }
+    }
+
+    override fun onWebsiteClicked(website: Website) {
+        if (!router.openBrowser(website)) {
+            view.showNoBrowserDialog()
+        }
+    }
+
+    override fun onInstallBrowserClicked(packageName: String) {
+        router.openMarket(packageName)
+    }
+
+    override fun onLegacyLoginClicked() {
+        permissions.requestPermission(Permission.PHONE)
+    }
+
+    override fun onPermissionResult(permissionResult: PermissionResult) {
+        permissions.submitResult(permissionResult)
+    }
+
+    override fun onGranted(permission: Permission) {
+        if (permission == Permission.PHONE) {
+            router.openLegacyLoginScreen()
+        }
+    }
+
+    override fun onBackClicked() {
+        router.close()
+    }
+
+}

+ 77 - 0
app/src/main/java/com/sirekanyan/knigopis/feature/login/LoginView.kt

@@ -0,0 +1,77 @@
+package com.sirekanyan.knigopis.feature.login
+
+import android.support.v7.app.AlertDialog
+import android.view.View
+import android.view.ViewGroup
+import com.sirekanyan.knigopis.R
+import com.sirekanyan.knigopis.common.extensions.inflate
+import kotlinx.android.extensions.LayoutContainer
+import kotlinx.android.synthetic.main.default_app_bar.*
+import kotlinx.android.synthetic.main.login_activity.*
+import kotlinx.android.synthetic.main.website_layout.view.*
+
+interface LoginView {
+
+    fun addWebsite(website: Website)
+    fun showNoBrowserDialog()
+
+    interface Callbacks {
+
+        fun onWebsiteClicked(website: Website)
+        fun onInstallBrowserClicked(packageName: String)
+        fun onLegacyLoginClicked()
+        fun onBackClicked()
+
+    }
+
+}
+
+class LoginViewImpl(
+    override val containerView: View,
+    private val callbacks: LoginView.Callbacks
+) : LoginView,
+    LayoutContainer {
+
+    private val context = containerView.context
+
+    init {
+        toolbar.setTitle(R.string.login_title)
+        toolbar.setNavigationIcon(R.drawable.ic_arrow_back)
+        toolbar.setNavigationOnClickListener { callbacks.onBackClicked() }
+        toolbar.inflateMenu(R.menu.login_options)
+        toolbar.setOnMenuItemClickListener { item ->
+            when (item.itemId) {
+                R.id.option_legacy_login -> {
+                    callbacks.onLegacyLoginClicked()
+                    true
+                }
+                else -> false
+            }
+        }
+    }
+
+    override fun addWebsite(website: Website) {
+        websitesContainer.addView(
+            websitesContainer.inflate<ViewGroup>(R.layout.website_layout).also { container ->
+                container.websiteTitle.setText(website.title)
+                container.websiteLogo.setImageResource(website.icon)
+                container.setOnClickListener {
+                    callbacks.onWebsiteClicked(website)
+                }
+            }
+        )
+    }
+
+    override fun showNoBrowserDialog() {
+        AlertDialog.Builder(context)
+            .setTitle(R.string.login_browser_title)
+            .setItems(R.array.login_browsers) { _, which ->
+                when (which) {
+                    0 -> callbacks.onInstallBrowserClicked("org.mozilla.firefox")
+                    1 -> callbacks.onInstallBrowserClicked("com.android.chrome")
+                }
+            }
+            .show()
+    }
+
+}

+ 17 - 0
app/src/main/java/com/sirekanyan/knigopis/feature/login/Website.kt

@@ -0,0 +1,17 @@
+package com.sirekanyan.knigopis.feature.login
+
+import com.sirekanyan.knigopis.R
+
+enum class Website(private val code: String, val color: Int, val title: Int, val icon: Int) {
+
+    VK("vkontakte", R.color.social_vk, R.string.login_website_vk, R.drawable.ic_login_vk),
+    GO("google", R.color.social_go, R.string.login_website_go, R.drawable.ic_login_go),
+    FB("facebook", R.color.social_fb, R.string.login_website_fb, R.drawable.ic_login_fb),
+    MR("mailru", R.color.social_mr, R.string.login_website_mr, R.drawable.ic_login_mr),
+    YA("yandex", R.color.social_ya, R.string.login_website_ya, R.drawable.ic_login_ya),
+    TW("twitter", R.color.social_tw, R.string.login_website_tw, R.drawable.ic_login_tw),
+    IN("instagram", R.color.social_in, R.string.login_website_in, R.drawable.ic_login_in);
+
+    val uri get() = buildLoginUri(code)
+
+}

+ 47 - 0
app/src/main/java/com/sirekanyan/knigopis/feature/login/constants.kt

@@ -0,0 +1,47 @@
+package com.sirekanyan.knigopis.feature.login
+
+import android.net.Uri
+import com.sirekanyan.knigopis.common.extensions.RANDOM_ID
+
+val LOGIN_CALLBACK_URI: Uri =
+    Uri.Builder()
+        .scheme("e270636c0efc6cad95130113d3bbafc3")
+        .authority("532b8e7fc54c52b6df5b55181acc241a")
+        .path("8e89f82ac2a6a7972452eae93e3bb734")
+        .build()
+private val LOGIN_URI: Uri =
+    Uri.Builder()
+        .scheme("https")
+        .authority("ulogin.ru")
+        .path("auth.php")
+        .build()
+private const val ULOGIN_PROVIDER_PARAM = "name"
+private val ULOGIN_OTHER_PARAMS =
+    mapOf(
+        "app_name" to "SDK",
+        "app_id" to "",
+        "secret_key" to "",
+        "mid" to "null_$RANDOM_ID",
+        "lang" to "en",
+        "fields" to "first_name,last_name",
+        "optional" to "",
+        "verify" to "",
+        "window" to "3",
+        "source" to "android",
+        "host" to "ulogin.ru",
+        "redirect_uri" to "",
+        "callback" to "ucall",
+        "screen" to "",
+        "providers" to "",
+        "q" to LOGIN_CALLBACK_URI.toString()
+    )
+
+fun buildLoginUri(provider: String): Uri =
+    LOGIN_URI.buildUpon()
+        .appendQueryParameter(ULOGIN_PROVIDER_PARAM, provider)
+        .apply {
+            ULOGIN_OTHER_PARAMS.forEach { (key, value) ->
+                appendQueryParameter(key, value)
+            }
+        }
+        .build()

+ 9 - 0
app/src/main/res/drawable/ic_login_fb.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="32.96"
+    android:viewportHeight="32.96">
+    <path
+        android:fillColor="#1877F2"
+        android:pathData="m24.092,18.4 l0.908,-5.92L19.32,12.48l0,-3.8417c0,-1.6196 0.7934,-3.1983 3.3375,-3.1983L25.24,5.44l0,-5.04c0,-0 -2.3437,-0.4 -4.5844,-0.4C15.9775,0 12.92,2.8352 12.92,7.968L12.92,12.48L7.72,12.48L7.72,18.4L12.92,18.4L12.92,32.96C12.92,32.96 15.0314,32.96 16.12,32.96c1.0886,-0 3.2,-0 3.2,-0L19.32,18.4l4.772,-0" />
+</vector>

+ 9 - 0
app/src/main/res/drawable/ic_login_go.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="18"
+    android:viewportHeight="18">
+    <path
+        android:fillColor="#EA4335"
+        android:pathData="m8.991,10.71v-3.348h8.424c0.126,0.567 0.225,1.098 0.225,1.845C17.64,14.346 14.193,18 9,18 4.032,18 0,13.968 0,9c0,-4.968 4.032,-9 9,-9 2.43,0 4.464,0.891 6.021,2.349l-2.556,2.484C11.817,4.221 10.683,3.501 9,3.501c-2.979,0 -5.409,2.475 -5.409,5.508 0,3.033 2.43,5.508 5.409,5.508 3.447,0 4.716,-2.385 4.95,-3.798L8.991,10.719Z" />
+</vector>

File diff suppressed because it is too large
+ 6 - 0
app/src/main/res/drawable/ic_login_in.xml


+ 9 - 0
app/src/main/res/drawable/ic_login_mr.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="32"
+    android:viewportHeight="32">
+    <path
+        android:fillColor="#FF9E00"
+        android:pathData="M20.813,16A4.818,4.818 0,0 1,16 20.813,4.818 4.818,0 0,1 11.187,16 4.818,4.818 0,0 1,16 11.187,4.818 4.818,0 0,1 20.813,16M16,0C7.178,0 0,7.178 0,16c0,8.822 7.178,16 16,16 3.232,0 6.349,-0.962 9.013,-2.783l0.046,-0.032 -2.156,-2.506 -0.036,0.024A12.672,12.672 0,0 1,16 28.72C8.986,28.72 3.28,23.014 3.28,16 3.28,8.986 8.986,3.28 16,3.28c7.014,0 12.72,5.706 12.72,12.72 0,0.909 -0.101,1.829 -0.3,2.734 -0.402,1.651 -1.558,2.157 -2.426,2.09C25.121,20.753 24.1,20.131 24.093,18.609V16c0,-4.463 -3.63,-8.093 -8.093,-8.093 -4.463,0 -8.093,3.63 -8.093,8.093 0,4.463 3.63,8.093 8.093,8.093a8.03,8.03 0,0 0,5.734 -2.389,5.198 5.198,0 0,0 3.997,2.389 5.399,5.399 0,0 0,3.678 -1.078c0.959,-0.728 1.675,-1.781 2.071,-3.046 0.063,-0.204 0.179,-0.672 0.18,-0.675l0.003,-0.017C31.896,18.262 32,17.25 32,16 32,7.178 24.822,0 16,0" />
+</vector>

+ 9 - 0
app/src/main/res/drawable/ic_login_tw.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="250"
+    android:viewportHeight="250">
+    <path
+        android:fillColor="#1DA1F2"
+        android:pathData="m78.62,226.569c94.34,0 145.94,-78.16 145.94,-145.94 0,-2.22 0,-4.43 -0.15,-6.63a104.36,104.36 0,0 0,25.59 -26.55,102.38 102.38,0 0,1 -29.46,8.07 51.47,51.47 0,0 0,22.55 -28.37,102.79 102.79,0 0,1 -32.57,12.45 51.34,51.34 0,0 0,-87.41 46.78,145.62 145.62,0 0,1 -105.71,-53.59 51.33,51.33 0,0 0,15.88 68.47,50.91 50.91,0 0,1 -23.28,-6.42c0,0.21 0,0.43 0,0.65a51.31,51.31 0,0 0,41.15 50.28,51.21 51.21,0 0,1 -23.16,0.88 51.35,51.35 0,0 0,47.92 35.62,102.92 102.92,0 0,1 -63.7,22 104.41,104.41 0,0 1,-12.21 -0.74,145.21 145.21,0 0,0 78.62,23" />
+</vector>

+ 9 - 0
app/src/main/res/drawable/ic_login_vk.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="94.422"
+    android:viewportHeight="94.422">
+    <path
+        android:fillColor="#4680C2"
+        android:pathData="m92.2001,23.011c0.7,-2.2 0,-3.8 -3.1,-3.8H78.8001c-2.6,0 -3.8,1.4 -4.5,2.9 0,0 -5.3,12.8 -12.7,21.1 -2.4,2.4 -3.5,3.2 -4.8,3.2 -0.7,0 -1.6,-0.8 -1.6,-3V22.911c0,-2.6 -0.8,-3.8 -3,-3.8H36c-1.6,0 -2.6,1.2 -2.6,2.4 0,2.5 3.7,3.1 4.1,10.1v15.2c0,3.3 -0.6,3.9 -1.9,3.9 -3.5,0 -12,-12.9 -17.1,-27.6 -1,-2.9 -2,-4 -4.6,-4H3.5c-3,0 -3.5,1.4 -3.5,2.9 0,2.7 3.5,16.3 16.3,34.3 8.5,12.3 20.6,18.9 31.5,18.9 6.6,0 7.4,-1.5 7.4,-4v-9.3c0,-3 0.6,-3.5 2.7,-3.5 1.5,0 4.2,0.8 10.3,6.7 7,7 8.2,10.2 12.1,10.2h10.3c3,0 4.4,-1.5 3.6,-4.4 -0.9,-2.9 -4.3,-7.1 -8.7,-12.1 -2.4,-2.8 -6,-5.9 -7.1,-7.4 -1.5,-2 -1.1,-2.8 0,-4.6 -0.1,0 12.5,-17.8 13.8,-23.8" />
+</vector>

+ 9 - 0
app/src/main/res/drawable/ic_login_ya.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="156.318"
+    android:viewportHeight="156.318">
+    <path
+        android:fillColor="#EE252C"
+        android:pathData="m99.202,14.211l-8.964,-0c-13.555,-0 -26.673,8.964 -26.673,34.98 0,25.143 12.025,33.232 26.673,33.232l8.964,-0zM85.21,96.416 L58.537,156.318L39.08,156.318l29.296,-64.058c-13.773,-6.996 -22.956,-19.676 -22.956,-43.069 0,-32.793 20.769,-49.191 45.475,-49.191l25.141,-0L116.036,156.318l-16.833,-0l0,-59.902l-13.992,-0" />
+</vector>

+ 24 - 0
app/src/main/res/layout/login_activity.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <include layout="@layout/default_app_bar" />
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipToPadding="false"
+        android:paddingTop="8dp"
+        android:paddingBottom="8dp">
+
+        <LinearLayout
+            android:id="@+id/websitesContainer"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical" />
+
+    </ScrollView>
+
+</LinearLayout>

+ 35 - 0
app/src/main/res/layout/website_layout.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="56dp"
+    android:background="?android:attr/selectableItemBackground"
+    android:clickable="true"
+    android:focusable="true">
+
+    <ImageView
+        android:id="@+id/websiteLogo"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_gravity="center_vertical"
+        android:layout_marginStart="16dp"
+        android:layout_marginLeft="16dp"
+        tools:ignore="ContentDescription"
+        tools:src="@drawable/ic_login_go" />
+
+    <TextView
+        android:id="@+id/websiteTitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginStart="72dp"
+        android:layout_marginLeft="72dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginRight="16dp"
+        android:ellipsize="end"
+        android:maxLines="1"
+        android:textColor="?android:textColorPrimary"
+        android:textSize="16sp"
+        tools:text="Google" />
+
+</FrameLayout>

+ 10 - 0
app/src/main/res/menu/login_options.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/option_legacy_login"
+        android:title="@string/login.legacy"
+        app:showAsAction="never" />
+
+</menu>

+ 16 - 0
app/src/main/res/values-ru/strings.xml

@@ -25,6 +25,22 @@
     <string name="main.option.dark">Тёмная тема</string>
     <string name="main.option.clear">Очистить кэш</string>
 
+    <!-- login -->
+    <string name="login.title">Войти через…</string>
+    <string name="login.legacy">Старая авторизация</string>
+    <string name="login.website.vk">Вконтакте</string>
+    <string name="login.website.go">Google</string>
+    <string name="login.website.fb">Фейсбук</string>
+    <string name="login.website.mr">Mail.ru</string>
+    <string name="login.website.ya">Яндекс</string>
+    <string name="login.website.tw">Твиттер</string>
+    <string name="login.website.in">Инстаграм</string>
+    <string name="login.browser.title">Не найден браузер</string>
+    <array name="login.browsers">
+        <item>Установить Firefox</item>
+        <item>Установить Chrome</item>
+    </array>
+
     <!-- about -->
     <string name="about.text.idea">API:\nhttp://knigopis.com</string>
     <string name="about.text.developer">Разработка:\nvadik@sirekanyan.com</string>

+ 9 - 0
app/src/main/res/values/colors.xml

@@ -66,4 +66,13 @@
     <!-- profile theme -->
     <color name="image_placeholder_color_dark">@color/common_deep_purple_300</color>
 
+    <!-- login -->
+    <color name="social_vk">#5181B8</color>
+    <color name="social_go">#4A8BF6</color>
+    <color name="social_fb">#3B5998</color>
+    <color name="social_mr">#005FF9</color>
+    <color name="social_ya">#FFDB4D</color>
+    <color name="social_tw">#1DA1F2</color>
+    <color name="social_in">#333333</color>
+
 </resources>

+ 16 - 0
app/src/main/res/values/strings.xml

@@ -24,6 +24,22 @@
     <string name="main.option.dark">Dark theme</string>
     <string name="main.option.clear">Clear cache</string>
 
+    <!-- login -->
+    <string name="login.title">Log in with…</string>
+    <string name="login.legacy">Legacy log in</string>
+    <string name="login.website.vk">VKontakte</string>
+    <string name="login.website.go">Google</string>
+    <string name="login.website.fb">Facebook</string>
+    <string name="login.website.mr">Mail.ru</string>
+    <string name="login.website.ya">Yandex</string>
+    <string name="login.website.tw">Twitter</string>
+    <string name="login.website.in">Instagram</string>
+    <string name="login.browser.title">No browsers found</string>
+    <array name="login.browsers">
+        <item>Install Firefox</item>
+        <item>Install Chrome</item>
+    </array>
+
     <!-- about -->
     <string name="about.text.idea">API:\nhttp://knigopis.com</string>
     <string name="about.text.developer">Development:\nvadik@sirekanyan.com</string>

Some files were not shown because too many files changed in this diff