每天舉行站立式會議
昨天已完成的工作
成員 |
任務 |
徐嘉煒 |
組織會議,說明專案進度,指導專案發展 |
陳祥意 |
參與會議,簡要講述應用程式測試的各個模組 |
林楦 |
參與會議,講述有關功能介面的UI開發 |
陳大鍇 |
參與會議,協調開發技術與實際需求,記錄需求 |
蔡家顯 |
參與會議,講述測試時的注意事項 |
陳祖民 |
參與會議,講述有關登入介面的UI開發 |
肖商 |
參與會議,詳細講述測試模組的測試用例 |
今天計劃完成的工作
成員 |
任務 |
徐嘉煒 |
開發登入介面互動邏輯 |
陳祥意 |
測試網路API |
林楦 |
開發註冊UI介面 |
陳大鍇 |
開發註冊介面互動邏輯 |
蔡家顯 |
彙總測試記錄 |
陳祖民 |
開發登入UI介面 |
肖商 |
生成各個測試模組的測試案例 |
工作中遇到的困難
UI介面與互動邏輯開發需要同步,所以需要UI組付出比較大的努力。
專案燃盡圖
適當的專案程式/模組的最新(執行)截圖
最新模組的程式碼
package com.timi.music
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
}
}
package com.timi.music
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.View.OnClickListener
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavController
import androidx.navigation.Navigation
import com.google.android.material.snackbar.Snackbar
import com.timi.music.databinding.FragmentLoginBinding
import com.timi.utils.Logger
import com.timi.utils.isClickEffective
import com.timi.utils.makeToast
class LoginFragment : Fragment(), OnClickListener {
companion object{
private const val TAG = "LoginFragment"
}
private lateinit var binding: FragmentLoginBinding
private lateinit var navController: NavController
private val loginViewModel: LoginViewModel by lazy {
ViewModelProvider(this)[LoginViewModel::class.java]
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentLoginBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
navController = Navigation.findNavController(view)
observeChange()
initListener()
if (Logger.currentLevel < 5){
binding.btnLoginFragmentTest.visibility = View.GONE
}
}
/**
* 檢測LoginViewModel中變數的變化
*/
private fun observeChange(){
loginViewModel.apply {
//驗證碼傳送返回結果
captchaCode.observe(viewLifecycleOwner){
if (it != 0){
if (it == 200){
Logger.i(TAG, "登入驗證碼傳送成功.")
makeToast("驗證碼傳送成功!")
}else{
Logger.i(TAG, "驗證碼傳送失敗.")
makeToast("驗證碼傳送失敗!請檢查你的手機號是否正確!")
}
captchaCode.value = 0 //重置變數
}
}
//遊客登陸返回結果
touristData.observe(viewLifecycleOwner){
it?.apply {
if (this.code == 200){
//登入後跳轉至主程式
Logger.i(TAG, "遊客登陸成功.")
makeToast("遊客登陸成功!")
//攜帶cookie跳轉至CentreActivity
startActivity(Intent(context, CentreActivity::class.java).apply {
//toString防止cookie為null引發空指標異常
putExtra("cookie", cookie.toString())
})
}else{
Logger.w(TAG, "遊客登陸失敗.")
makeToast("遊客登陸失敗")
}
touristData.value = null
}
}
}
}
private fun initListener(){
binding.apply {
btnLoginFragmentLogin.setOnClickListener(this@LoginFragment)
btnLoginFragmentRegister.setOnClickListener(this@LoginFragment)
btnLoginFragmentSendCode.setOnClickListener(this@LoginFragment)
btnLoginFragmentTouristLogin.setOnClickListener(this@LoginFragment)
binding.btnLoginFragmentTest.setOnClickListener(this@LoginFragment)
}
}
override fun onClick(v: View?) {
when(v){
binding.btnLoginFragmentLogin -> {
if (checkInput()){
// if (tryLogin()){
//
// }
}
}
binding.btnLoginFragmentRegister -> navController.navigate(R.id.action_loginFragment_to_registerFragment)
binding.btnLoginFragmentSendCode -> getCode()
binding.btnLoginFragmentTouristLogin -> {
if (v.isClickEffective()) loginViewModel.touristLoginRequest()
}
binding.btnLoginFragmentTest -> {
if (v.isClickEffective()){
startActivity(Intent(context, CentreActivity::class.java).apply {
putExtra("cookie", "")
})
}
}
}
}
private fun getCode(){
if (binding.edtLoginFragmentPhoneNumber.text.length != 11){
//如果輸入格式不滿足要求,無法獲取驗證碼
makeToast("請輸入11位手機號!")
}else{
loginViewModel.codeRequest(binding.edtLoginFragmentPhoneNumber.text.toString())
}
}
private fun checkInput(): Boolean{
return if (binding.edtLoginFragmentPhoneNumber.text.toString().isNotEmpty()){
if (binding.edtLoginFragmentCode.text.toString().isNotEmpty()){
true
}else{
Snackbar.make(requireView(), "請輸入驗證碼!", Snackbar.LENGTH_SHORT).show()
false
}
}else{
Snackbar.make(requireView(), "請輸入手機號!", Snackbar.LENGTH_SHORT).show()
false
}
}
// private fun tryLogin(): Boolean{
//
// }
}
package com.timi.music
import com.timi.music.bean.Captcha
import com.timi.music.bean.CaptchaService
import com.timi.music.bean.Tourist
import com.timi.music.bean.TouristService
import com.timi.utils.Logger
import com.timi.utils.ServiceBuilder
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class LoginModel {
private val TAG = "LoginModel"
/**
* 發出獲取驗證碼請求
*/
fun codeRequest(
loginVM: LoginViewModel,
phoneNumber: String
){
val captchaService = ServiceBuilder.create(CaptchaService::class.java)
captchaService.getCaptchaData(phoneNumber).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
response.body()?.apply {
Logger.i(TAG, "註冊驗證碼請求:$this")
loginVM.captchaCode.value = this.code
}
}
override fun onFailure(call: Call, t: Throwable) {
t.printStackTrace()
}
})
}
/**
* 進行遊客登陸
*/
fun touristLoginRequest(loginVM: LoginViewModel){
val touristService = ServiceBuilder.create(TouristService::class.java)
touristService.getTouristData().enqueue(object : Callback{
override fun onResponse(call: Call, response: Response) {
response.body()?.apply {
Logger.i(TAG, "遊客登陸請求:$this")
loginVM.touristData.value = this
}
}
override fun onFailure(call: Call, t: Throwable) {
t.printStackTrace()
}
})
}
}
package com.timi.music
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.timi.music.bean.Tourist
class LoginViewModel: ViewModel() {
private val TAG = "LoginViewModel"
private val loginModel: LoginModel by lazy { LoginModel() }
val captchaCode = MutableLiveData() //驗證碼傳送返回結果
val touristData = MutableLiveData() //遊客登陸返回結果
init {
captchaCode.value = 0
touristData.value = null
}
/**
* 發出獲取驗證碼請求
*/
fun codeRequest(phoneNumber: String){
loginModel.codeRequest(this, phoneNumber)
}
fun touristLoginRequest(){
loginModel.touristLoginRequest(this)
}
}
package com.timi.music
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.View.OnClickListener
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavController
import androidx.navigation.Navigation
import com.timi.music.databinding.FragmentRegisterBinding
import com.timi.utils.Logger
import com.timi.utils.makeToast
class RegisterFragment : Fragment(), OnClickListener{
companion object {
private const val TAG = "RegisterFragment"
}
private lateinit var binding: FragmentRegisterBinding
private lateinit var navigationController: NavController
private val registerViewModel: RegisterViewModel by lazy {
ViewModelProvider(this)[RegisterViewModel::class.java]
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentRegisterBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
navigationController = Navigation.findNavController(view)
observeChange()
initListener()
}
/**
* 檢測RegisterViewModel中變數的變化
*/
private fun observeChange(){
registerViewModel.apply {
captchaCode.observe(viewLifecycleOwner){
if (it != 0){
if (it == 200){
Logger.i(TAG, "註冊驗證碼傳送成功.")
makeToast("驗證碼傳送成功!")
}else{
Logger.i(TAG, "驗證碼傳送失敗.")
makeToast("驗證碼傳送失敗!請檢查你的手機號是否正確!")
}
captchaCode.value = 0 //重置變數
}
}
}
}
private fun initListener(){
binding.ivRegisterFragmentBack.setOnClickListener(this)
binding.btnRegisterFragmentGetCode.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v){
binding.ivRegisterFragmentBack -> navigationController.navigateUp() //返回到LoginFragment
binding.btnRegisterFragmentGetCode -> getCode() //請求傳送註冊驗證碼
}
}
/**
* 獲取手機驗證碼
*/
private fun getCode(){
if (binding.edtRegisterFragmentPhoneNumber.text.length != 11){
//如果輸入格式不滿足要求,無法獲取驗證碼
makeToast("請輸入11位手機號!")
}else{
registerViewModel.codeRequest(binding.edtRegisterFragmentPhoneNumber.text.toString())
}
}
}
package com.timi.music
import android.util.Log
import com.timi.music.bean.Captcha
import com.timi.music.bean.CaptchaService
import com.timi.utils.ServiceBuilder
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class RegisterModel {
private val TAG = "RegisterModel"
/**
* 發出獲取驗證碼請求
*/
fun codeRequest(
registerVM: RegisterViewModel,
phoneNumber: String
){
val captchaService = ServiceBuilder.create(CaptchaService::class.java)
captchaService.getCaptchaData(phoneNumber).enqueue(object : Callback{
override fun onFailure(call: Call, t: Throwable) {
t.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.body()?.apply {
Log.i(TAG, "註冊驗證碼請求:${this}")
registerVM.captchaCode.value = this.code
}
}
})
}
}
package com.timi.music
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class RegisterViewModel: ViewModel() {
private val registerModel: RegisterModel by lazy { RegisterModel() }
val captchaCode = MutableLiveData() //驗證碼傳送返回的結果
init {
captchaCode.value = 0
}
/**
* 發出獲取驗證碼請求
*/
fun codeRequest(phoneNumber: String){
registerModel.codeRequest(this, phoneNumber)
}
}
執行結果的截圖
每日每人總結
成員 |
總結 |
徐嘉煒 |
登入註冊模組已經做得比較完善,團隊各個小組都發揮了各自的優勢 |
陳祥意 |
測試模組已經全部透過,可以進行下一個模組的測試 |
林楦 |
基礎的UI介面已經定好,可以在不改變資料物件的前提下繼續美化UI介面 |
陳大鍇 |
程式碼在邏輯上基本沒有問題,也說是有一點成就,希望下一個模組的開發也順順利利。 |
蔡家顯 |
在測試的方式上,可以寫一些自動化指令碼進行測試,但是手寫測試案例還是有用的,希望能夠學到更多的測試方法 |
陳祖民 |
UI介面的互動比較簡潔明瞭,是一個有點,可以往簡約風格方面發展,期待主功能介面的UI介面開發 |
肖商 |
測試用例傳送和接收的報文均為有效報文,測試成功的喜悅促使著我們進一步開發下一個模組 |