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