diff --git a/_config.yml b/_config.yml
new file mode 100644
index 0000000..c419263
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-cayman
\ No newline at end of file
diff --git a/code/AndroidPlay/.gitignore b/code/AndroidPlay/.gitignore
new file mode 100644
index 0000000..603b140
--- /dev/null
+++ b/code/AndroidPlay/.gitignore
@@ -0,0 +1,14 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
diff --git a/code/AndroidPlay/.idea/codeStyles/Project.xml b/code/AndroidPlay/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..88ea3aa
--- /dev/null
+++ b/code/AndroidPlay/.idea/codeStyles/Project.xml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/AndroidPlay/.idea/codeStyles/codeStyleConfig.xml b/code/AndroidPlay/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/code/AndroidPlay/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/code/AndroidPlay/.idea/gradle.xml b/code/AndroidPlay/.idea/gradle.xml
new file mode 100644
index 0000000..169fd0d
--- /dev/null
+++ b/code/AndroidPlay/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/AndroidPlay/.idea/misc.xml b/code/AndroidPlay/.idea/misc.xml
new file mode 100644
index 0000000..37a7509
--- /dev/null
+++ b/code/AndroidPlay/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/AndroidPlay/.idea/runConfigurations.xml b/code/AndroidPlay/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/code/AndroidPlay/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/AndroidPlay/.idea/vcs.xml b/code/AndroidPlay/.idea/vcs.xml
new file mode 100644
index 0000000..b2bdec2
--- /dev/null
+++ b/code/AndroidPlay/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/AndroidPlay/app/.gitignore b/code/AndroidPlay/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/code/AndroidPlay/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/code/AndroidPlay/app/build.gradle b/code/AndroidPlay/app/build.gradle
new file mode 100644
index 0000000..b955a28
--- /dev/null
+++ b/code/AndroidPlay/app/build.gradle
@@ -0,0 +1,42 @@
+apply plugin: 'com.android.application'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.2"
+ defaultConfig {
+ applicationId "com.quin.android.play"
+ minSdkVersion 19
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ dataBinding {
+ enabled true
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.core:core-ktx:1.1.0'
+ implementation 'com.google.android.material:material:1.0.0'
+ implementation 'androidx.annotation:annotation:1.0.2'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test:runner:1.2.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+
+}
diff --git a/code/AndroidPlay/app/proguard-rules.pro b/code/AndroidPlay/app/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/code/AndroidPlay/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/code/AndroidPlay/app/src/androidTest/java/com/quin/android/play/ExampleInstrumentedTest.kt b/code/AndroidPlay/app/src/androidTest/java/com/quin/android/play/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..ba5c7ea
--- /dev/null
+++ b/code/AndroidPlay/app/src/androidTest/java/com/quin/android/play/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.quin.android.play
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.quin.android.play", appContext.packageName)
+ }
+}
diff --git a/code/AndroidPlay/app/src/main/AndroidManifest.xml b/code/AndroidPlay/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..dabfb9a
--- /dev/null
+++ b/code/AndroidPlay/app/src/main/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/LoginDataSource.kt b/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/LoginDataSource.kt
new file mode 100644
index 0000000..120abf3
--- /dev/null
+++ b/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/LoginDataSource.kt
@@ -0,0 +1,25 @@
+package com.quin.android.play.data
+
+import com.quin.android.play.data.model.LoggedInUser
+import java.io.IOException
+
+/**
+ * Class that handles authentication w/ onLoginClicked credentials and retrieves user information.
+ */
+class LoginDataSource {
+
+ fun login(username: String, password: String): Result {
+ try {
+ // TODO: handle loggedInUser authentication
+ val fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe")
+ return Result.Success(fakeUser)
+ } catch (e: Throwable) {
+ return Result.Error(IOException("Error logging in", e))
+ }
+ }
+
+ fun logout() {
+ // TODO: revoke authentication
+ }
+}
+
diff --git a/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/LoginRepository.kt b/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/LoginRepository.kt
new file mode 100644
index 0000000..8fa1861
--- /dev/null
+++ b/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/LoginRepository.kt
@@ -0,0 +1,46 @@
+package com.quin.android.play.data
+
+import com.quin.android.play.data.model.LoggedInUser
+
+/**
+ * Class that requests authentication and user information from the remote data source and
+ * maintains an in-memory cache of onLoginClicked status and user credentials information.
+ */
+
+class LoginRepository(val dataSource: LoginDataSource) {
+
+ // in-memory cache of the loggedInUser object
+ var user: LoggedInUser? = null
+ private set
+
+ val isLoggedIn: Boolean
+ get() = user != null
+
+ init {
+ // If user credentials will be cached in local storage, it is recommended it be encrypted
+ // @see https://developer.android.com/training/articles/keystore
+ user = null
+ }
+
+ fun logout() {
+ user = null
+ dataSource.logout()
+ }
+
+ fun login(username: String, password: String): Result {
+ // handle onLoginClicked
+ val result = dataSource.login(username, password)
+
+ if (result is Result.Success) {
+ setLoggedInUser(result.data)
+ }
+
+ return result
+ }
+
+ private fun setLoggedInUser(loggedInUser: LoggedInUser) {
+ this.user = loggedInUser
+ // If user credentials will be cached in local storage, it is recommended it be encrypted
+ // @see https://developer.android.com/training/articles/keystore
+ }
+}
diff --git a/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/Result.kt b/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/Result.kt
new file mode 100644
index 0000000..5414781
--- /dev/null
+++ b/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/Result.kt
@@ -0,0 +1,18 @@
+package com.quin.android.play.data
+
+/**
+ * A generic class that holds a value with its loading status.
+ * @param
+ */
+sealed class Result {
+
+ data class Success(val data: T) : Result()
+ data class Error(val exception: Exception) : Result()
+
+ override fun toString(): String {
+ return when (this) {
+ is Success<*> -> "Success[data=$data]"
+ is Error -> "Error[exception=$exception]"
+ }
+ }
+}
diff --git a/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/model/LoggedInUser.kt b/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/model/LoggedInUser.kt
new file mode 100644
index 0000000..7d03f7f
--- /dev/null
+++ b/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/model/LoggedInUser.kt
@@ -0,0 +1,9 @@
+package com.quin.android.play.data.model
+
+/**
+ * Data class that captures user information for logged in users retrieved from LoginRepository
+ */
+data class LoggedInUser(
+ val userId: String,
+ val displayName: String
+)
diff --git a/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/model/LoginUserModel.kt b/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/model/LoginUserModel.kt
new file mode 100644
index 0000000..c99ea8e
--- /dev/null
+++ b/code/AndroidPlay/app/src/main/java/com/quin/android/play/data/model/LoginUserModel.kt
@@ -0,0 +1,16 @@
+package com.quin.android.play.data.model
+
+/**
+ * Description:登录用户数据用例
+ * Created by Quinin on 2020-01-04.
+ **/
+data class LoginUserModel(val userName: String?, val userPsw: String?) {
+
+ fun isUserNameValid(): Boolean {
+ return !userName.isNullOrBlank()
+ }
+
+ fun isPswValid(): Boolean {
+ return !userPsw.isNullOrEmpty() && userPsw.length > 5
+ }
+}
\ No newline at end of file
diff --git a/code/AndroidPlay/app/src/main/java/com/quin/android/play/ui/login/LoggedInUserView.kt b/code/AndroidPlay/app/src/main/java/com/quin/android/play/ui/login/LoggedInUserView.kt
new file mode 100644
index 0000000..cc8e8cf
--- /dev/null
+++ b/code/AndroidPlay/app/src/main/java/com/quin/android/play/ui/login/LoggedInUserView.kt
@@ -0,0 +1,9 @@
+package com.quin.android.play.ui.login
+
+/**
+ * User details post authentication that is exposed to the UI
+ */
+data class LoggedInUserView(
+ val displayName: String
+ //... other data fields that may be accessible to the UI
+)
diff --git a/code/AndroidPlay/app/src/main/java/com/quin/android/play/ui/login/LoginActivity.kt b/code/AndroidPlay/app/src/main/java/com/quin/android/play/ui/login/LoginActivity.kt
new file mode 100644
index 0000000..e669a51
--- /dev/null
+++ b/code/AndroidPlay/app/src/main/java/com/quin/android/play/ui/login/LoginActivity.kt
@@ -0,0 +1,183 @@
+package com.quin.android.play.ui.login
+
+import android.app.Activity
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProviders
+import android.os.Bundle
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AppCompatActivity
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.View
+import android.view.inputmethod.EditorInfo
+import android.widget.Button
+import android.widget.EditText
+import android.widget.ProgressBar
+import android.widget.Toast
+import androidx.databinding.DataBindingUtil
+
+import com.quin.android.play.R
+import com.quin.android.play.data.model.LoginUserModel
+import com.quin.android.play.databinding.ActivityLoginBinding
+import kotlinx.android.synthetic.main.activity_login.*
+
+class LoginActivity : AppCompatActivity() {
+
+ private lateinit var loginViewModel: LoginViewModel
+ private lateinit var dataBinding: ActivityLoginBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+
+ dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_login)
+
+ loginViewModel = ViewModelProviders.of(this, LoginViewModelFactory())
+ .get(LoginViewModel::class.java)
+
+ dataBinding.lifecycleOwner = this
+ dataBinding.loginViewModel = loginViewModel
+
+
+ loginViewModel.loginUserNameLiveData.observe(this, object : Observer {
+ override fun onChanged(t: String?) {
+ if (t.isNullOrBlank()) {
+ dataBinding.username.error = getString(R.string.invalid_username)
+ dataBinding.username.requestFocus()
+ dataBinding.login.isEnabled = false
+ }
+ }
+ })
+
+ loginViewModel.loginUserPswLiveData.observe(this, object : Observer {
+ override fun onChanged(t: String?) {
+ if (t.isNullOrEmpty()) {
+ dataBinding.password.error = getString(R.string.invalid_password)
+ dataBinding.password.requestFocus()
+ dataBinding.login.isEnabled = false
+ }else if(t.length<5)
+ {
+ dataBinding.password.error = getString(R.string.invalid_userpsw)
+ dataBinding.password.requestFocus()
+ dataBinding.login.isEnabled = false
+ }else{
+ dataBinding.login.isEnabled = true
+ }
+ }
+ })
+
+ loginViewModel.getLoginUserModel().observe(this,
+ Observer {
+ it?.apply {
+
+ loginViewModel.login()
+ Toast.makeText(
+ this@LoginActivity,
+ "Welcome,$userName!",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ })
+ /*setContentView(R.layout.activity_login)
+
+ val username = findViewById(R.id.username)
+ val password = findViewById(R.id.password)
+ val login = findViewById