Browse Source

Merge remote-tracking branch 'remotes/origin/develop'

# Conflicts:
#	app/build.gradle
#	app/src/main/AndroidManifest.xml
lqg 1 year ago
parent
commit
1978c74310
49 changed files with 1298 additions and 230 deletions
  1. 1 0
      .gitignore
  2. 7 0
      README.md
  3. 25 9
      app/build.gradle
  4. 24 4
      app/src/main/AndroidManifest.xml
  5. 25 0
      app/src/main/java/me/yoqi/android/netauth/App.java
  6. 51 2
      app/src/main/java/me/yoqi/android/netauth/MainActivity.java
  7. 39 0
      app/src/main/java/me/yoqi/android/netauth/data/LoginDataSource.java
  8. 62 0
      app/src/main/java/me/yoqi/android/netauth/data/LoginRepository.java
  9. 48 0
      app/src/main/java/me/yoqi/android/netauth/data/Result.java
  10. 23 0
      app/src/main/java/me/yoqi/android/netauth/data/model/LoggedInUser.java
  11. 18 0
      app/src/main/java/me/yoqi/android/netauth/model/Account.java
  12. 19 0
      app/src/main/java/me/yoqi/android/netauth/model/Config.java
  13. 94 0
      app/src/main/java/me/yoqi/android/netauth/service/QSTileService.java
  14. 17 0
      app/src/main/java/me/yoqi/android/netauth/ui/login/LoggedInUserView.java
  15. 131 0
      app/src/main/java/me/yoqi/android/netauth/ui/login/LoginActivity.java
  16. 41 0
      app/src/main/java/me/yoqi/android/netauth/ui/login/LoginFormState.java
  17. 31 0
      app/src/main/java/me/yoqi/android/netauth/ui/login/LoginResult.java
  18. 94 0
      app/src/main/java/me/yoqi/android/netauth/ui/login/LoginViewModel.java
  19. 26 0
      app/src/main/java/me/yoqi/android/netauth/ui/login/LoginViewModelFactory.java
  20. 30 0
      app/src/main/java/me/yoqi/android/netauth/utils/MD5.java
  21. 36 0
      app/src/main/java/me/yoqi/android/netauth/utils/NtutNet.java
  22. 86 0
      app/src/main/java/me/yoqi/android/utils/NetUtils.java
  23. 22 0
      app/src/main/java/me/yoqi/android/utils/RegexParser.java
  24. 78 0
      app/src/main/java/me/yoqi/android/utils/SPUtils.java
  25. 80 0
      app/src/main/java/me/yoqi/android/utils/SimplexToast.java
  26. 10 0
      app/src/main/java/me/yoqi/android/utils/StringUtils.java
  27. 0 30
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  28. 0 170
      app/src/main/res/drawable/ic_launcher_background.xml
  29. 76 0
      app/src/main/res/layout/activity_login.xml
  30. 19 4
      app/src/main/res/layout/activity_main.xml
  31. 0 5
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  32. 0 5
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  33. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  34. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  35. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  36. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  37. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  38. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  39. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  40. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  41. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  42. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  43. 21 0
      app/src/main/res/values-zh/strings.xml
  44. 12 0
      app/src/main/res/values/arrays.xml
  45. 5 0
      app/src/main/res/values/dimens.xml
  46. 20 0
      app/src/main/res/values/strings.xml
  47. 9 1
      app/src/main/res/values/styles.xml
  48. 2 0
      build.gradle
  49. 16 0
      config.bak.gradle

+ 1 - 0
.gitignore

@@ -13,3 +13,4 @@
 .externalNativeBuild
 .cxx
 /.idea/
+/config.gradle

+ 7 - 0
README.md

@@ -0,0 +1,7 @@
+## NetAuth
+
+通大校园网认证app,手机自动登录,一键上网。用完一键断网。
+
+
+## screenshot
+

+ 25 - 9
app/build.gradle

@@ -1,34 +1,50 @@
 apply plugin: 'com.android.application'
 
+def build = rootProject.ext.build
+def sign = rootProject.ext.sign
+
 android {
-    compileSdkVersion 33
+    compileSdkVersion build.compileSdkVersion
+    signingConfigs {
+        release {
+            storeFile file(sign.storeFile)
+            keyAlias sign.keyAlias
+            keyPassword sign.keyPassword
+            storePassword sign.storePassword
+        }
+    }
+    compileSdkVersion build.compileSdkVersion
 
     defaultConfig {
-        applicationId "me.yoqi.android.netauth"
-        minSdkVersion 15
-        targetSdkVersion 33
-        versionCode 10001
-        versionName "1.0.1"
+        applicationId build.applicationId
+        minSdkVersion build.minSdkVersion
+        targetSdkVersion build.targetSdkVersion
+        versionCode build.versionCode
+        versionName build.versionName
 
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }
 
     buildTypes {
         release {
-            minifyEnabled false
+            minifyEnabled true
             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+            signingConfig signingConfigs.release
         }
+        debug{
+            minifyEnabled false
     }
 }
+}
 
 dependencies {
     implementation fileTree(dir: "libs", include: ["*.jar"])
-    
+ 
     implementation 'androidx.appcompat:appcompat:1.5.0'
     implementation 'com.google.android.material:material:1.6.1'
     implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
     testImplementation 'junit:junit:4.13.2'
     androidTestImplementation 'androidx.test.ext:junit:1.1.3'
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
-
+    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
 }

+ 24 - 4
app/src/main/AndroidManifest.xml

@@ -1,22 +1,42 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="me.yoqi.android.netauth">
+    <!-- 获取网络状态 -->
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <!-- 存储权限 -->
+    <uses-permission
+        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+        tools:ignore="ScopedStorage" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 
     <application
+        android:name=".App"
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
-        android:roundIcon="@mipmap/ic_launcher_round"
-        android:supportsRtl="true"
         android:theme="@style/AppTheme">
-        <activity android:name=".MainActivity"
-        android:exported="true">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
 
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity android:name=".ui.login.LoginActivity" />
+
+        <service
+            android:name=".service.QSTileService"
+            android:exported="true"
+            android:icon="@drawable/abc_vector_test"
+            android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.service.quicksettings.action.QS_TILE" />
+            </intent-filter>
+        </service>
     </application>
 
 </manifest>

+ 25 - 0
app/src/main/java/me/yoqi/android/netauth/App.java

@@ -0,0 +1,25 @@
+package me.yoqi.android.netauth;
+
+import android.app.Application;
+import android.content.Context;
+
+/**
+ * @author liuyuqi.gov@msn.cn
+ * @created 2020-10-17
+ */
+public class App extends Application {
+    Context mContext;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mContext = this;
+    }
+
+    /**
+     * //todo
+     */
+    public void init(){
+
+    }
+}

+ 51 - 2
app/src/main/java/me/yoqi/android/netauth/MainActivity.java

@@ -1,19 +1,68 @@
 package me.yoqi.android.netauth;
 
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
 import androidx.appcompat.app.AppCompatActivity;
 
-import android.os.Bundle;
+import me.yoqi.android.netauth.ui.login.LoginActivity;
+import me.yoqi.android.utils.SPUtils;
 
+/**
+ * inter point
+ */
 public class MainActivity extends AppCompatActivity {
+    Button btnLogin;
+    TextView tvStatus;
+    SPUtils spUtils;
+    Context mContext;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
 
+        mContext = this;
+        spUtils = new SPUtils(mContext);
         initView();
     }
-    void initView(){
 
+    void initView() {
+        btnLogin = findViewById(R.id.btn_login);
+        tvStatus = findViewById(R.id.tv_status);
+
+        btnLogin.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                gotoLogin();
+            }
+        });
+        changeStatus();
+    }
+
+    void gotoLogin() {
+        //跳转到登录界面
+        Intent intent = new Intent(mContext, LoginActivity.class);
+        startActivity(intent);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        changeStatus();
+    }
+
+    void changeStatus() {
+        boolean netStatus = spUtils.getBoolean("status", false);
+        if (netStatus) {
+            tvStatus.setText("登录成功");
+        } else {
+            tvStatus.setText("未登录");
+            gotoLogin();
+        }
     }
 }

+ 39 - 0
app/src/main/java/me/yoqi/android/netauth/data/LoginDataSource.java

@@ -0,0 +1,39 @@
+package me.yoqi.android.netauth.data;
+
+import java.io.IOException;
+
+import me.yoqi.android.netauth.data.model.LoggedInUser;
+
+/**
+ * Class that handles authentication w/ login credentials and retrieves user information.
+ */
+public class LoginDataSource {
+
+    /**
+     * 登录
+     *
+     * @param username 用户名
+     * @param password 密码
+     * @return
+     */
+    public Result<LoggedInUser> login(String username, String password) {
+
+        try {
+            // TODO: handle loggedInUser authentication
+            LoggedInUser fakeUser =
+                    new LoggedInUser(
+                            java.util.UUID.randomUUID().toString(),
+                            "Jane Doe");
+            return new Result.Success<>(fakeUser);
+        } catch (Exception e) {
+            return new Result.Error(new IOException("Error logging in", e));
+        }
+    }
+
+    /**
+     * 退出
+     */
+    public void logout() {
+        // TODO: revoke authentication
+    }
+}

+ 62 - 0
app/src/main/java/me/yoqi/android/netauth/data/LoginRepository.java

@@ -0,0 +1,62 @@
+package me.yoqi.android.netauth.data;
+
+import me.yoqi.android.netauth.data.model.LoggedInUser;
+
+/**
+ * 数据层面操作
+ * Class that requests authentication and user information from the remote data source and
+ * maintains an in-memory cache of login status and user credentials information.
+ */
+public class LoginRepository {
+
+    private static volatile LoginRepository instance;
+
+    private final LoginDataSource dataSource;
+
+    // If user credentials will be cached in local storage, it is recommended it be encrypted
+    // @see https://developer.android.com/training/articles/keystore
+    private LoggedInUser user = null;
+
+    // private constructor : singleton access
+    private LoginRepository(LoginDataSource dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    public static LoginRepository getInstance(LoginDataSource dataSource) {
+        if (instance == null) {
+            instance = new LoginRepository(dataSource);
+        }
+        return instance;
+    }
+
+    public boolean isLoggedIn() {
+        return user != null;
+    }
+
+    public void logout() {
+        user = null;
+        dataSource.logout();
+    }
+
+    private void setLoggedInUser(LoggedInUser user) {
+        this.user = user;
+        // If user credentials will be cached in local storage, it is recommended it be encrypted
+        // @see https://developer.android.com/training/articles/keystore
+    }
+
+    /**
+     * 登录
+     *
+     * @param username 用户名
+     * @param password 密码
+     * @return 登录成功
+     */
+    public Result<LoggedInUser> login(String username, String password) {
+        // handle login
+        Result<LoggedInUser> result = dataSource.login(username, password);
+        if (result instanceof Result.Success) {
+            setLoggedInUser(((Result.Success<LoggedInUser>) result).getData());
+        }
+        return result;
+    }
+}

+ 48 - 0
app/src/main/java/me/yoqi/android/netauth/data/Result.java

@@ -0,0 +1,48 @@
+package me.yoqi.android.netauth.data;
+
+/**
+ * A generic class that holds a result success w/ data or an error exception.
+ */
+public class Result<T> {
+    // hide the private constructor to limit subclass types (Success, Error)
+    private Result() {
+    }
+
+    @Override
+    public String toString() {
+        if (this instanceof Result.Success) {
+            Result.Success success = (Result.Success) this;
+            return "Success[data=" + success.getData().toString() + "]";
+        } else if (this instanceof Result.Error) {
+            Result.Error error = (Result.Error) this;
+            return "Error[exception=" + error.getError().toString() + "]";
+        }
+        return "";
+    }
+
+    // Success sub-class
+    public final static class Success<T> extends Result {
+        private T data;
+
+        public Success(T data) {
+            this.data = data;
+        }
+
+        public T getData() {
+            return this.data;
+        }
+    }
+
+    // Error sub-class
+    public final static class Error extends Result {
+        private Exception error;
+
+        public Error(Exception error) {
+            this.error = error;
+        }
+
+        public Exception getError() {
+            return this.error;
+        }
+    }
+}

+ 23 - 0
app/src/main/java/me/yoqi/android/netauth/data/model/LoggedInUser.java

@@ -0,0 +1,23 @@
+package me.yoqi.android.netauth.data.model;
+
+/**
+ * 用户表
+ */
+public class LoggedInUser {
+
+    private final String userId; //id
+    private final String displayName; // 用户名
+
+    public LoggedInUser(String userId, String displayName) {
+        this.userId = userId;
+        this.displayName = displayName;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+}

+ 18 - 0
app/src/main/java/me/yoqi/android/netauth/model/Account.java

@@ -0,0 +1,18 @@
+package me.yoqi.android.netauth.model;
+
+import android.media.Image;
+
+import java.io.Serializable;
+
+/**
+ * 用户账号信息
+ *
+ * @author liuyuqi.gov@msn.cn
+ * @created 2020-10-17
+ */
+public class Account implements Serializable {
+    String userName;
+    String pwd;
+    Image logo;//头像
+    int sex;
+}

+ 19 - 0
app/src/main/java/me/yoqi/android/netauth/model/Config.java

@@ -0,0 +1,19 @@
+package me.yoqi.android.netauth.model;
+
+import android.os.Build;
+
+/**
+ * 通用配置
+ *
+ * @author liuyuqi.gov@msn.cn
+ * @created 2020-10-17
+ */
+public class Config {
+    int SDK = Build.VERSION.SDK_INT;
+
+    String LOG_TAG = "ONE_TAP_CDUT_NET";
+
+    // 校园网登录地址
+    String CAMPUS_NET_URL = "http://172.20.255.252";
+
+}

+ 94 - 0
app/src/main/java/me/yoqi/android/netauth/service/QSTileService.java

@@ -0,0 +1,94 @@
+package me.yoqi.android.netauth.service;
+
+import android.os.AsyncTask;
+import android.os.Build;
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
+
+import androidx.annotation.RequiresApi;
+
+import me.yoqi.android.netauth.R;
+import me.yoqi.android.netauth.utils.NtutNet;
+import me.yoqi.android.utils.NetUtils;
+import me.yoqi.android.utils.SPUtils;
+
+/** 上网认证服务
+ * @author liuyuqi.gov@msn.cn
+ * @created 2020-10-17
+ */
+@RequiresApi(api = Build.VERSION_CODES.N)
+public class QSTileService extends TileService {
+
+    SPUtils spUtils;
+    Tile tile;
+    NtutNet cdutNet = new NtutNet();
+
+    /**
+     * 图标可见回调
+     */
+    @Override
+    public void onStartListening() {
+        super.onStartListening();
+        if (NetUtils.isWifiConnected(this)) {
+            updateStatus(R.string.tile_label, false);
+        } else {// 未连接 WiFi 则将图块置为不可用状态
+            updateStatus(R.string.tile_status_no_net_conn, false);
+        }
+    }
+
+    @Override
+    public void onClick() {
+        super.onClick();
+        tile = getQsTile();
+        new MyTask().execute();
+    }
+
+    /**
+     * 更新状态
+     *
+     * @param labelId
+     * @param enable
+     */
+    void updateStatus(int labelId, boolean enable) {
+        tile = getQsTile();
+        tile.setLabel(getString(labelId));
+        if (enable) {
+            tile.setState(Tile.STATE_ACTIVE); //登录校园网成功
+        } else {
+            tile.setState(Tile.STATE_UNAVAILABLE);
+        }
+        tile.updateTile();
+    }
+
+    /**
+     * 异步登录
+     */
+    class MyTask extends AsyncTask<String, Integer, Boolean> {
+
+        @Override
+        protected Boolean doInBackground(String... strings) {
+            //更新状态
+            publishProgress(R.string.tile_status_check);
+            Boolean res = cdutNet.check();
+            if (res == null) {
+                publishProgress(R.string.tile_status_not); // 未连接校园网
+                return false;
+            } else if (res) {
+                publishProgress(R.string.tile_status_ok); //登录成功
+                return true;
+            } else {
+                //执行登录
+                if (cdutNet.login("ss", "236")) {
+                    publishProgress(R.string.tile_status_failed); //登录失败
+                    return false;
+                } else return true;
+            }
+        }
+
+        @Override
+        protected void onProgressUpdate(Integer... values) {
+            super.onProgressUpdate(values);
+//            updateStatus(values[0], false);
+        }
+    }
+}

+ 17 - 0
app/src/main/java/me/yoqi/android/netauth/ui/login/LoggedInUserView.java

@@ -0,0 +1,17 @@
+package me.yoqi.android.netauth.ui.login;
+
+/**
+ * Class exposing authenticated user details to the UI.
+ */
+class LoggedInUserView {
+    private final String displayName;
+    //... other data fields that may be accessible to the UI
+
+    LoggedInUserView(String displayName) {
+        this.displayName = displayName;
+    }
+
+    String getDisplayName() {
+        return displayName;
+    }
+}

+ 131 - 0
app/src/main/java/me/yoqi/android/netauth/ui/login/LoginActivity.java

@@ -0,0 +1,131 @@
+package me.yoqi.android.netauth.ui.login;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProviders;
+
+import me.yoqi.android.netauth.R;
+
+/**
+ * 登录界面
+ */
+public class LoginActivity extends AppCompatActivity {
+
+    private LoginViewModel loginViewModel;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_login);
+
+        loginViewModel = ViewModelProviders.of(this, new LoginViewModelFactory()).get(LoginViewModel.class);
+
+
+//        sp中取得保存的账户密码,填充。多个账户的话下拉提供选择
+
+        final EditText usernameEditText = findViewById(R.id.username);
+        final EditText passwordEditText = findViewById(R.id.password);
+        final Button loginButton = findViewById(R.id.login);   //登录按钮
+        final ProgressBar loadingProgressBar = findViewById(R.id.loading);
+
+//        信息验证
+        loginViewModel.getLoginFormState().observe(this, new Observer<LoginFormState>() {
+            @Override
+            public void onChanged(@Nullable LoginFormState loginFormState) {
+                if (loginFormState == null) {
+                    return;
+                }
+                loginButton.setEnabled(loginFormState.isDataValid());
+                if (loginFormState.getUsernameError() != null) {
+                    usernameEditText.setError(getString(loginFormState.getUsernameError()));
+                }
+                if (loginFormState.getPasswordError() != null) {
+                    passwordEditText.setError(getString(loginFormState.getPasswordError()));
+                }
+            }
+        });
+
+        loginViewModel.getLoginResult().observe(this, new Observer<LoginResult>() {
+            @Override
+            public void onChanged(@Nullable LoginResult loginResult) {
+                if (loginResult == null) {
+                    return;
+                }
+                loadingProgressBar.setVisibility(View.GONE);
+                if (loginResult.getError() != null) {
+                    showLoginFailed(loginResult.getError());
+                }
+                if (loginResult.getSuccess() != null) {
+                    updateUiWithUser(loginResult.getSuccess());
+                }
+                setResult(Activity.RESULT_OK);
+                finish();
+            }
+        });
+
+        TextWatcher afterTextChangedListener = new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                // ignore
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                // ignore
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+                loginViewModel.loginDataChanged(usernameEditText.getText().toString(),
+                        passwordEditText.getText().toString());
+            }
+        };
+        usernameEditText.addTextChangedListener(afterTextChangedListener);
+        passwordEditText.addTextChangedListener(afterTextChangedListener);
+        passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                if (actionId == EditorInfo.IME_ACTION_DONE) {
+                    loginViewModel.login(usernameEditText.getText().toString(),
+                            passwordEditText.getText().toString());
+                }
+                return false;
+            }
+        });
+        // 点击事件
+        loginButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                loadingProgressBar.setVisibility(View.VISIBLE);
+                loginViewModel.login(usernameEditText.getText().toString(),
+                        passwordEditText.getText().toString());
+            }
+        });
+    }
+
+    private void updateUiWithUser(LoggedInUserView model) {
+        String welcome = getString(R.string.welcome) + model.getDisplayName();
+        // TODO : initiate successful logged in experience
+        Toast.makeText(getApplicationContext(), welcome, Toast.LENGTH_LONG).show();
+    }
+
+    private void showLoginFailed(@StringRes Integer errorString) {
+        Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show();
+    }
+}

+ 41 - 0
app/src/main/java/me/yoqi/android/netauth/ui/login/LoginFormState.java

@@ -0,0 +1,41 @@
+package me.yoqi.android.netauth.ui.login;
+
+import androidx.annotation.Nullable;
+
+/**
+ * 表单验证.
+ */
+class LoginFormState {
+
+    @Nullable
+    private final Integer usernameError;
+    @Nullable
+    private final Integer passwordError;
+    private final boolean isDataValid;
+
+    LoginFormState(@Nullable Integer usernameError, @Nullable Integer passwordError) {
+        this.usernameError = usernameError;
+        this.passwordError = passwordError;
+        this.isDataValid = false;
+    }
+
+    LoginFormState(boolean isDataValid) {
+        this.usernameError = null;
+        this.passwordError = null;
+        this.isDataValid = isDataValid;
+    }
+
+    @Nullable
+    Integer getUsernameError() {
+        return usernameError;
+    }
+
+    @Nullable
+    Integer getPasswordError() {
+        return passwordError;
+    }
+
+    boolean isDataValid() {
+        return isDataValid;
+    }
+}

+ 31 - 0
app/src/main/java/me/yoqi/android/netauth/ui/login/LoginResult.java

@@ -0,0 +1,31 @@
+package me.yoqi.android.netauth.ui.login;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Authentication result : success (user details) or error message.
+ */
+class LoginResult {
+    @Nullable
+    private LoggedInUserView success;
+    @Nullable
+    private Integer error;
+
+    LoginResult(@Nullable Integer error) {
+        this.error = error;
+    }
+
+    LoginResult(@Nullable LoggedInUserView success) {
+        this.success = success;
+    }
+
+    @Nullable
+    LoggedInUserView getSuccess() {
+        return success;
+    }
+
+    @Nullable
+    Integer getError() {
+        return error;
+    }
+}

+ 94 - 0
app/src/main/java/me/yoqi/android/netauth/ui/login/LoginViewModel.java

@@ -0,0 +1,94 @@
+package me.yoqi.android.netauth.ui.login;
+
+import android.util.Patterns;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+import me.yoqi.android.netauth.R;
+import me.yoqi.android.netauth.data.LoginRepository;
+import me.yoqi.android.netauth.data.Result;
+import me.yoqi.android.netauth.data.model.LoggedInUser;
+
+/**
+ *
+ */
+public class LoginViewModel extends ViewModel {
+
+    private final MutableLiveData<LoginFormState> loginFormState = new MutableLiveData<>();
+    private final MutableLiveData<LoginResult> loginResult = new MutableLiveData<>();
+    private LoginRepository loginRepository;
+
+    LoginViewModel(LoginRepository loginRepository) {
+        this.loginRepository = loginRepository;
+    }
+
+    /**
+     * @return
+     */
+    LiveData<LoginFormState> getLoginFormState() {
+        return loginFormState;
+    }
+
+    /**
+     * 获取登录结果
+     *
+     * @return
+     */
+    LiveData<LoginResult> getLoginResult() {
+        return loginResult;
+    }
+
+    /**
+     * 登录
+     *
+     * @param username 用户名
+     * @param password 密码
+     */
+    public void login(String username, String password) {
+        Result<LoggedInUser> result = loginRepository.login(username, password);
+
+        if (result instanceof Result.Success) {
+            LoggedInUser data = ((Result.Success<LoggedInUser>) result).getData();
+            loginResult.setValue(new LoginResult(new LoggedInUserView(data.getDisplayName())));
+            //登录成功,跳转到 MainActivity
+
+        } else {
+            loginResult.setValue(new LoginResult(R.string.login_failed));
+        }
+    }
+
+    /**
+     * 密码框更改事件
+     *
+     * @param username
+     * @param password
+     */
+    public void loginDataChanged(String username, String password) {
+        if (!isUserNameValid(username)) {
+            loginFormState.setValue(new LoginFormState(R.string.invalid_username, null));
+        } else if (!isPasswordValid(password)) {
+            loginFormState.setValue(new LoginFormState(null, R.string.invalid_password));
+        } else {
+            loginFormState.setValue(new LoginFormState(true));
+        }
+    }
+
+    // A placeholder username validation check
+    private boolean isUserNameValid(String username) {
+        if (username == null) {
+            return false;
+        }
+        if (username.contains("@")) {
+            return Patterns.EMAIL_ADDRESS.matcher(username).matches();
+        } else {
+            return !username.trim().isEmpty();
+        }
+    }
+
+    // A placeholder password validation check
+    private boolean isPasswordValid(String password) {
+        return password != null && password.trim().length() > 5;
+    }
+}

+ 26 - 0
app/src/main/java/me/yoqi/android/netauth/ui/login/LoginViewModelFactory.java

@@ -0,0 +1,26 @@
+package me.yoqi.android.netauth.ui.login;
+
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.annotation.NonNull;
+
+import me.yoqi.android.netauth.data.LoginDataSource;
+import me.yoqi.android.netauth.data.LoginRepository;
+
+/**
+ * ViewModel provider factory to instantiate LoginViewModel.
+ * Required given LoginViewModel has a non-empty constructor
+ */
+public class LoginViewModelFactory implements ViewModelProvider.Factory {
+
+    @NonNull
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+        if (modelClass.isAssignableFrom(LoginViewModel.class)) {
+            return (T) new LoginViewModel(LoginRepository.getInstance(new LoginDataSource()));
+        } else {
+            throw new IllegalArgumentException("Unknown ViewModel class");
+        }
+    }
+}

+ 30 - 0
app/src/main/java/me/yoqi/android/netauth/utils/MD5.java

@@ -0,0 +1,30 @@
+package me.yoqi.android.netauth.utils;
+
+import java.io.ByteArrayInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * 加密工具类,网络传输过程中,账号信息加密传输
+ *
+ * @author liuyuqi.gov@msn.cn
+ * @created 2020-10-17
+ */
+public class MD5 {
+
+    String md5(String str) throws NoSuchAlgorithmException {
+        MessageDigest md5 = MessageDigest.getInstance("MD5");
+        md5.update(str.getBytes());
+        return null;
+    }
+
+    String prase(ByteArrayInputStream bytes) {
+        // TODO: 2020-10-17
+        return null;
+    }
+
+    String parseHex(ByteArrayInputStream bytes) {
+        StringBuilder sb = new StringBuilder();
+        return sb.toString();
+    }
+}

+ 36 - 0
app/src/main/java/me/yoqi/android/netauth/utils/NtutNet.java

@@ -0,0 +1,36 @@
+package me.yoqi.android.netauth.utils;
+
+import android.util.Log;
+
+/**
+ * @author liuyuqi.gov@msn.cn
+ * @created 2020-10-17
+ */
+public class NtutNet {
+    String tag = "CduNet";
+
+    /**
+     * 检测校园网状态
+     *
+     * @return null 未连接校园网
+     * false 已连接校园网,但未登录
+     * true 已链接校园网,登登录成功
+     */
+    public Boolean check() {
+        // TODO: 2020-10-17 检测网络状态
+        return false;
+    }
+
+    /**
+     * 执行登录
+     *
+     * @param userName
+     * @param pwd
+     * @return
+     */
+    public boolean login(String userName, String pwd) {
+        Log.i(tag, "login: user+pwd" + userName + ":" + pwd);
+        // TODO: 2020-10-17 执行登录,如果登陆成功,返回true
+        return false;
+    }
+}

+ 86 - 0
app/src/main/java/me/yoqi/android/utils/NetUtils.java

@@ -0,0 +1,86 @@
+package me.yoqi.android.utils;
+
+import android.app.Service;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.os.Build;
+import android.util.Log;
+
+/**
+ * 网络检测工具类
+ *
+ * @author liuyuqi.gov@msn.cn
+ * @createTime 2020-08-18
+ */
+public class NetUtils {
+    //检测网络是否可用,wifi or gps
+
+    /**
+     * 判断 WiFi 连接
+     */
+    public static boolean isWifiConnected(Context context) {
+        if (context == null) return false;
+        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Service.CONNECTIVITY_SERVICE);
+        if (connectivityManager != null) {
+            if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
+                if (capabilities != null) {
+                    if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+                        return true;
+                    }
+                }
+            } else {
+                try {
+                    NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
+                    if (activeNetworkInfo != null && activeNetworkInfo.isConnected() && activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+                        Log.i("update_statut", "Network is available : true");
+                        return true;
+                    }
+                } catch (Exception e) {
+                    Log.i("update_statut", "" + e.getMessage());
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 检测网络是否链接
+     *
+     * @param context 上下文
+     * @return
+     */
+    public static boolean isNetworkAvailable(Context context) {
+        if (context == null) return false;
+        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        if (connectivityManager != null) {
+            if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
+                if (capabilities != null) {
+                    if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+                        return true;
+                    } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+                        return true;
+                    } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
+                        return true;
+                    }
+                }
+            } else {
+                try {
+                    NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
+                    if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
+                        Log.i("update_statut", "Network is available : true");
+                        return true;
+                    }
+                } catch (Exception e) {
+                    Log.i("update_statut", "" + e.getMessage());
+                }
+            }
+        }
+        Log.i("update_statut", "Network is available : FALSE ");
+        return false;
+    }
+}
+

+ 22 - 0
app/src/main/java/me/yoqi/android/utils/RegexParser.java

@@ -0,0 +1,22 @@
+package me.yoqi.android.utils;
+
+import android.util.Patterns;
+
+/**
+ * 常用正则表达式
+ *
+ * @author liuyuqi.gov@msn.cn
+ * @createTime 2020-08-17
+ */
+public class RegexParser {
+    static final String PHONE_PATTERN = Patterns.PHONE.pattern();
+    static final String EMAIL_PATTERN = Patterns.EMAIL_ADDRESS.pattern();
+
+    //at、话题、链接匹配表达式
+    static final String MENTION_PATTERN = "@.{1,15}?\\s";
+    static final String HASHTAG_PATTERN = "#.{1,15}?\\s";
+    static final String URL_PATTERN = "(http|https|ftp|svn)://([a-zA-Z0-9]+[/?.?])" +
+            "+[a-zA-Z0-9]*\\??([a-zA-Z0-9]*=[a-zA-Z0-9]*&?)*";
+
+}
+

+ 78 - 0
app/src/main/java/me/yoqi/android/utils/SPUtils.java

@@ -0,0 +1,78 @@
+package me.yoqi.android.utils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+
+import java.util.Map;
+import java.util.Set;
+
+public class SPUtils {
+    private static final String SP_NAME = "common";
+    private static SPUtils mSpUtils;
+    private SharedPreferences sp;
+    private Editor editor;
+
+    public SPUtils(Context mContext) {
+        sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
+        editor = sp.edit();
+    }
+
+    public static SPUtils getInstance(Context context) {
+
+        if (mSpUtils == null) {
+            synchronized (SPUtils.class) {
+                if (mSpUtils == null) {
+                    mSpUtils = new SPUtils(context);
+                    return mSpUtils;
+                }
+            }
+        }
+
+        return mSpUtils;
+
+    }
+
+    public void putBoolean(String key, Boolean value) {
+        editor.putBoolean(key, value);
+        editor.commit();
+    }
+
+    public boolean getBoolean(String key, Boolean defValue) {
+        return sp.getBoolean(key, defValue);
+    }
+
+    public void putString(String key, String value) {
+        if (key == null) {
+            return;
+        }
+        editor.putString(key, value);
+        editor.commit();
+    }
+
+    public String getString(String key, String defValue) {
+        return sp.getString(key, defValue);
+    }
+
+    public Set<String> getStringSet(String key, Set<String> defValue) {
+        return sp.getStringSet(key, defValue);
+    }
+
+    public void putInt(String key, int value) {
+        editor.putInt(key, value);
+        editor.commit();
+    }
+
+    public int getInt(String key, int defValue) {
+        return sp.getInt(key, defValue);
+    }
+
+    public Map<String, ?> getAll() {
+        return sp.getAll();
+    }
+
+    public void remove(String key) {
+        sp.edit().remove(key).apply();
+    }
+
+}

+ 80 - 0
app/src/main/java/me/yoqi/android/utils/SimplexToast.java

@@ -0,0 +1,80 @@
+package me.yoqi.android.utils;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.widget.Toast;
+
+/**
+ * 以后请用这个吐司,谢谢!!!
+ * <p>
+ * <p>
+ * {@link Toast}的创建都是要inflate一个layout, findViewById之类的
+ * 将一个吐司单例化,并且作防止频繁点击的处理。
+ * <p>
+ * <p>
+ */
+@SuppressWarnings("all")
+public class SimplexToast {
+
+    private static Toast mToast;
+    private static long nextTimeMillis;
+    private static int yOffset;
+    static Context mContext;
+    private static SimplexToast simplexToast;
+
+    public synchronized static SimplexToast getInstance(Context context) {
+        if (simplexToast == null) {
+            simplexToast = new SimplexToast(context);
+        }
+        return simplexToast;
+    }
+
+    public SimplexToast(Context context) {
+        mContext = context;
+    }
+
+    public static Toast init(Context context) {
+        if (context == null) {
+            throw new IllegalArgumentException("Context should not be null!!!");
+        }
+        if (mToast == null) {
+            mToast = Toast.makeText(context, null, Toast.LENGTH_SHORT);
+            yOffset = mToast.getYOffset();
+        }
+        mToast.setDuration(Toast.LENGTH_SHORT);
+        mToast.setGravity(Gravity.BOTTOM, 0, yOffset);
+        mToast.setMargin(0, 0);
+        return mToast;
+    }
+
+    public static void show(String content) {
+        show(content, Toast.LENGTH_SHORT);
+    }
+
+    public static void show(String content, int duration) {
+        show(mContext, content, Gravity.BOTTOM, duration);
+    }
+
+    public static void show(Context context, int rid) {
+        show(context, context.getResources().getString(rid));
+    }
+
+    public static void show(Context context, String content) {
+        show(context, content, Gravity.BOTTOM);
+    }
+
+    public static void show(Context context, String content, int gravity) {
+        show(context, content, gravity, Toast.LENGTH_SHORT);
+    }
+
+    public static void show(Context context, String content, int gravity, int duration) {
+        long current = System.currentTimeMillis();
+        //if (current < nextTimeMillis) return;
+        if (mToast == null) init(context.getApplicationContext());
+        mToast.setText(content);
+        mToast.setDuration(duration);
+        mToast.setGravity(gravity, 0, yOffset);
+        nextTimeMillis = current + (duration == Toast.LENGTH_LONG ? 3500 : 2000);
+        mToast.show();
+    }
+}

+ 10 - 0
app/src/main/java/me/yoqi/android/utils/StringUtils.java

@@ -0,0 +1,10 @@
+package me.yoqi.android.utils;
+
+/**
+ * @author liuyuqi.gov@msn.cn
+ * @createTime 2020-08-17
+ */
+public class StringUtils {
+
+}
+

+ 0 - 30
app/src/main/res/drawable-v24/ic_launcher_foreground.xml

@@ -1,30 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportWidth="108"
-    android:viewportHeight="108">
-    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
-        <aapt:attr name="android:fillColor">
-            <gradient
-                android:endX="85.84757"
-                android:endY="92.4963"
-                android:startX="42.9492"
-                android:startY="49.59793"
-                android:type="linear">
-                <item
-                    android:color="#44000000"
-                    android:offset="0.0" />
-                <item
-                    android:color="#00000000"
-                    android:offset="1.0" />
-            </gradient>
-        </aapt:attr>
-    </path>
-    <path
-        android:fillColor="#FFFFFF"
-        android:fillType="nonZero"
-        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
-        android:strokeWidth="1"
-        android:strokeColor="#00000000" />
-</vector>

+ 0 - 170
app/src/main/res/drawable/ic_launcher_background.xml

@@ -1,170 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportWidth="108"
-    android:viewportHeight="108">
-    <path
-        android:fillColor="#3DDC84"
-        android:pathData="M0,0h108v108h-108z" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M9,0L9,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,0L19,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,0L29,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,0L39,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,0L49,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,0L59,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,0L69,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,0L79,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M89,0L89,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M99,0L99,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,9L108,9"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,19L108,19"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,29L108,29"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,39L108,39"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,49L108,49"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,59L108,59"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,69L108,69"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,79L108,79"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,89L108,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,99L108,99"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,29L89,29"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,39L89,39"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,49L89,49"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,59L89,59"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,69L89,69"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,79L89,79"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,19L29,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,19L39,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,19L49,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,19L59,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,19L69,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,19L79,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-</vector>

+ 76 - 0
app/src/main/res/layout/activity_login.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    tools:context=".ui.login.LoginActivity">
+
+    <EditText
+        android:id="@+id/username"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="24dp"
+        android:layout_marginTop="96dp"
+        android:layout_marginEnd="24dp"
+        android:hint="@string/prompt_account"
+        android:inputType="text"
+        android:selectAllOnFocus="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <EditText
+        android:id="@+id/password"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="24dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="24dp"
+        android:hint="@string/prompt_password"
+        android:imeActionLabel="@string/action_sign_in_short"
+        android:imeOptions="actionDone"
+        android:inputType="textPassword"
+        android:selectAllOnFocus="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/username" />
+
+    <Button
+        android:id="@+id/login"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start"
+        android:layout_marginStart="48dp"
+        android:layout_marginTop="16dp"
+        android:layout_marginEnd="48dp"
+        android:layout_marginBottom="64dp"
+        android:enabled="false"
+        android:text="@string/action_sign_in"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/password"
+        app:layout_constraintVertical_bias="0.2" />
+
+    <ProgressBar
+        android:id="@+id/loading"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginStart="32dp"
+        android:layout_marginTop="64dp"
+        android:layout_marginEnd="32dp"
+        android:layout_marginBottom="64dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="@+id/password"
+        app:layout_constraintStart_toStartOf="@+id/password"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="0.3" />
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 19 - 4
app/src/main/res/layout/activity_main.xml

@@ -7,12 +7,27 @@
     tools:context=".MainActivity">
 
     <TextView
+        android:id="@+id/tv_status"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:text="Hello World!"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toRightOf="parent"
+        android:layout_marginTop="250dp"
+        android:text="未登录"
+        android:textSize="45dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
 
+    <Button
+        android:id="@+id/btn_login"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="30dp"
+        android:text="登录"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/tv_status" />
+
+
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 0 - 5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>

+ 0 - 5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


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

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">校园网</string>
+    <string name="title_activity_login">登录</string>
+    <string name="prompt_account">账户</string>
+    <string name="prompt_password">密码</string>
+    <string name="action_sign_in">登录</string>
+    <string name="action_sign_in_short">登录</string>
+    <string name="welcome">欢迎</string>
+    <string name="invalid_username">用户名错误</string>
+    <string name="invalid_password">密码错误</string>
+    <string name="login_failed">登录失败</string>
+    <string name="tile_label">authnet</string>
+    <string name="tile_status_ok">成功</string>
+    <string name="tile_status_failed">失败</string>
+    <string name="tile_status_no_net_conn">未连接</string>
+    <string name="tile_status_not">不</string>
+    <string name="how_to">添加到快速启动</string>
+    <string name="tile_status_loggin">登录中…</string>
+    <string name="tile_status_check">检测中…</string>
+</resources>

+ 12 - 0
app/src/main/res/values/arrays.xml

@@ -0,0 +1,12 @@
+<resources>
+    <!-- Reply Preference -->
+    <string-array name="reply_entries">
+        <item>Reply</item>
+        <item>Reply to all</item>
+    </string-array>
+
+    <string-array name="reply_values">
+        <item>reply</item>
+        <item>reply_all</item>
+    </string-array>
+</resources>

+ 5 - 0
app/src/main/res/values/dimens.xml

@@ -0,0 +1,5 @@
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>

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

@@ -1,3 +1,23 @@
 <resources>
     <string name="app_name">NetAuth</string>
+    <string name="title_activity_login">Login In</string>
+    <string name="prompt_account">Account</string>
+    <string name="prompt_password">Password</string>
+    <string name="action_sign_in">Sign in</string>
+    <string name="action_sign_in_short">Sign in</string>
+    <string name="welcome">"Welcome !"</string>
+    <string name="invalid_username">Not a valid username</string>
+    <string name="invalid_password">Password must be >5 characters</string>
+    <string name="login_failed">"Login failed"</string>
+
+
+    <string name="tile_label">CdutNet</string>
+    <string name="tile_status_check">Checking&#8230;</string>
+    <string name="tile_status_loggin">Logining&#8230;</string>
+    <string name="tile_status_ok">OK</string>
+    <string name="tile_status_not">Not</string>
+    <string name="tile_status_failed">Failed</string>
+    <string name="tile_status_no_net_conn">NoNetConn</string>
+    <string name="how_to">Add the tile \"CdutNet\" in Quick Settings to use.</string>
+
 </resources>

+ 9 - 1
app/src/main/res/values/styles.xml

@@ -6,5 +6,13 @@
         <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
         <item name="colorAccent">@color/colorAccent</item>
     </style>
-
+    <!--无标题栏-->
+    <style name="AppTheme.NoTitleBar" parent="AppTheme">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">false</item>
+    </style>
+    <!--    全屏-->
+    <style name="AppTheme.FullScreen" parent="AppTheme">
+        <item name="android:windowFullscreen">true</item>
+    </style>
 </resources>

+ 2 - 0
build.gradle

@@ -1,4 +1,6 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
+apply from:'config.gradle'
+
 buildscript {
     repositories {
         google()

+ 16 - 0
config.bak.gradle

@@ -0,0 +1,16 @@
+ext {
+    build = [
+            applicationId   : "me.yoqi.android.netauth",
+            minSdkVersion   : 21,
+            targetSdkVersion: 33,
+            compileSdkVersion:33,
+            versionCode     : 10001,
+            versionName     : "1.0.1",
+    ]
+    sign = [
+            keyAlias     : "签名别名",
+            keyPassword  : "签名密码",
+            storeFile    : "签名文件路径",
+            storePassword: "签名文件密码",
+    ]
+}