From 356ecb74af5aed7e00999934f87416349fefa9ba Mon Sep 17 00:00:00 2001 From: lzz Date: Fri, 2 Sep 2016 16:02:54 +0800 Subject: [PATCH 001/181] add login activity --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 7 + .../com/loopeer/codereader/Navigator.java | 5 + .../codereader/api/ServiceFactory.java | 1 + .../codereader/api/service/GithubService.java | 19 ++ .../com/loopeer/codereader/model/Empty.java | 4 + .../com/loopeer/codereader/model/Token.java | 150 ++++++++++++ .../codereader/ui/activity/LoginActivity.java | 141 ++++++++++++ .../codereader/ui/activity/MainActivity.java | 4 + .../codereader/ui/view/LoginChecker.java | 28 +++ .../com/loopeer/codereader/utils/Base64.java | 216 ++++++++++++++++++ .../codereader/utils/SnackbarUtils.java | 18 ++ .../res/drawable-xxhdpi/ic_menu_github.png | Bin 0 -> 1471 bytes .../res/drawable-xxhdpi/ic_pwd_unvisible.png | Bin 0 -> 1288 bytes .../res/drawable-xxhdpi/ic_pwd_visible.png | Bin 0 -> 1272 bytes .../res/drawable/selector_pwd_visibility.xml | 7 + app/src/main/res/layout/activity_login.xml | 55 +++++ app/src/main/res/menu/menu_github.xml | 9 + app/src/main/res/values/strings.xml | 8 + gradle.properties | 1 + 20 files changed, 674 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/loopeer/codereader/model/Empty.java create mode 100644 app/src/main/java/com/loopeer/codereader/model/Token.java create mode 100644 app/src/main/java/com/loopeer/codereader/ui/activity/LoginActivity.java create mode 100644 app/src/main/java/com/loopeer/codereader/ui/view/LoginChecker.java create mode 100755 app/src/main/java/com/loopeer/codereader/utils/Base64.java create mode 100644 app/src/main/java/com/loopeer/codereader/utils/SnackbarUtils.java create mode 100644 app/src/main/res/drawable-xxhdpi/ic_menu_github.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_pwd_unvisible.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_pwd_visible.png create mode 100644 app/src/main/res/drawable/selector_pwd_visibility.xml create mode 100644 app/src/main/res/layout/activity_login.xml create mode 100644 app/src/main/res/menu/menu_github.xml diff --git a/app/build.gradle b/app/build.gradle index 369a13f..2204b11 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,7 +31,7 @@ dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:' + support_version - compile 'com.android.support:design:' + support_version + compile 'com.android.support:design:' + support_design_version compile 'com.jakewharton:butterknife:' + butterknife_version apt 'com.jakewharton:butterknife-compiler:' + butterknife_version compile 'com.squareup.retrofit2:retrofit:' + retrofit diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 30d2968..bba9214 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -95,6 +95,13 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".ui.activity.MainActivity"/> + + + \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/Navigator.java b/app/src/main/java/com/loopeer/codereader/Navigator.java index 35e4cb4..10b5fc8 100644 --- a/app/src/main/java/com/loopeer/codereader/Navigator.java +++ b/app/src/main/java/com/loopeer/codereader/Navigator.java @@ -13,6 +13,7 @@ import com.loopeer.codereader.ui.activity.AboutActivity; import com.loopeer.codereader.ui.activity.AddRepoActivity; import com.loopeer.codereader.ui.activity.CodeReadActivity; +import com.loopeer.codereader.ui.activity.LoginActivity; import com.loopeer.codereader.ui.activity.MainActivity; import com.loopeer.codereader.ui.activity.SearchActivity; import com.loopeer.codereader.ui.activity.SettingActivity; @@ -120,4 +121,8 @@ public static void startOutWebActivity(Context context, String url) { context.startActivity(intent); } + public static void startLoginActivity(Context context){ + Intent intent = new Intent(context, LoginActivity.class); + context.startActivity(intent); + } } diff --git a/app/src/main/java/com/loopeer/codereader/api/ServiceFactory.java b/app/src/main/java/com/loopeer/codereader/api/ServiceFactory.java index 09dfa3e..3ffd289 100644 --- a/app/src/main/java/com/loopeer/codereader/api/ServiceFactory.java +++ b/app/src/main/java/com/loopeer/codereader/api/ServiceFactory.java @@ -8,4 +8,5 @@ public static GithubService getGithubService() { return ServiceUtils.getApiService().getRetrofit().create(GithubService.class); } + } diff --git a/app/src/main/java/com/loopeer/codereader/api/service/GithubService.java b/app/src/main/java/com/loopeer/codereader/api/service/GithubService.java index e97cbbf..1525d64 100644 --- a/app/src/main/java/com/loopeer/codereader/api/service/GithubService.java +++ b/app/src/main/java/com/loopeer/codereader/api/service/GithubService.java @@ -1,11 +1,20 @@ package com.loopeer.codereader.api.service; import com.loopeer.codereader.api.BaseListResponse; +import com.loopeer.codereader.model.Empty; import com.loopeer.codereader.model.Repository; +import com.loopeer.codereader.model.Token; + +import java.util.List; import okhttp3.ResponseBody; import retrofit2.Response; +import retrofit2.http.Body; +import retrofit2.http.DELETE; import retrofit2.http.GET; +import retrofit2.http.Header; +import retrofit2.http.POST; +import retrofit2.http.Path; import retrofit2.http.Query; import retrofit2.http.Streaming; import retrofit2.http.Url; @@ -26,5 +35,15 @@ Observable>> repositories( @Query("per_page") int pageSize ); + // Api about token + @POST("authorizations") + Observable> createToken(@Body Token token, @Header("Authorization") String authorization); + + @GET("authorizations") + Observable>> listToken(@Header("Authorization") String authorization); + + @DELETE("authorizations/{id}") + Observable> removeToken(@Header("Authorization") String authorization, @Path("id") String id); + } diff --git a/app/src/main/java/com/loopeer/codereader/model/Empty.java b/app/src/main/java/com/loopeer/codereader/model/Empty.java new file mode 100644 index 0000000..a8560aa --- /dev/null +++ b/app/src/main/java/com/loopeer/codereader/model/Empty.java @@ -0,0 +1,4 @@ +package com.loopeer.codereader.model; + +public class Empty { +} diff --git a/app/src/main/java/com/loopeer/codereader/model/Token.java b/app/src/main/java/com/loopeer/codereader/model/Token.java new file mode 100644 index 0000000..c1359f6 --- /dev/null +++ b/app/src/main/java/com/loopeer/codereader/model/Token.java @@ -0,0 +1,150 @@ +package com.loopeer.codereader.model; + +import java.util.List; + + +/** + * + * @author Quinn + * + */ +public class Token { + private int id; + private String url; + private List scopes; + private String token; + private String hashed_token; + private String token_last_eight; + private String note; + private String note_url; + private String created_at; + private String updated_at; + private String fingerprint; + private App app; + private static class App{ + private String name; + private String url; + private String client_id; + + public App(){ + + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getClient_id() { + return client_id; + } + + public void setClient_id(String client_id) { + this.client_id = client_id; + } + + @Override + public String toString() { + return "app [name=" + name + ", url=" + url + ", client_id=" + + client_id + "]"; + } + + } + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + public String getUrl() { + return url; + } + public void setUrl(String url) { + this.url = url; + } + public List getScopes() { + return scopes; + } + public void setScopes(List scropes) { + this.scopes = scropes; + } + public String getToken() { + return token; + } + public void setToken(String token) { + this.token = token; + } + public String getHashed_token() { + return hashed_token; + } + public void setHashed_token(String hashed_token) { + this.hashed_token = hashed_token; + } + public String getToken_last_eight() { + return token_last_eight; + } + public void setToken_last_eight(String token_last_eight) { + this.token_last_eight = token_last_eight; + } + public String getNote() { + return note; + } + public void setNote(String note) { + this.note = note; + } + public String getNote_url() { + return note_url; + } + public void setNote_url(String note_url) { + this.note_url = note_url; + } + public String getCreated_at() { + return created_at; + } + public void setCreated_at(String created_at) { + this.created_at = created_at; + } + public String getUpdated_at() { + return updated_at; + } + public void setUpdated_at(String updated_at) { + this.updated_at = updated_at; + } + public String getFingerprint() { + return fingerprint; + } + public void setFingerprint(String fingerprint) { + this.fingerprint = fingerprint; + } + public App getApp() { + return app; + } + public void setApp(App _app) { + this.app = _app; + } + + @Override + public String toString() { + return "Token [id=" + id + ", url=" + url + ", scopes=" + scopes + + ", token=" + token + ", hashed_token=" + hashed_token + + ", token_last_eight=" + token_last_eight + ", note=" + note + + ", note_url=" + note_url + ", created_at=" + created_at + + ", updated_at=" + updated_at + ", fingerprint=" + fingerprint + + ", app=" + app + "]"; + } + + + +} diff --git a/app/src/main/java/com/loopeer/codereader/ui/activity/LoginActivity.java b/app/src/main/java/com/loopeer/codereader/ui/activity/LoginActivity.java new file mode 100644 index 0000000..7fccc0d --- /dev/null +++ b/app/src/main/java/com/loopeer/codereader/ui/activity/LoginActivity.java @@ -0,0 +1,141 @@ +package com.loopeer.codereader.ui.activity; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.text.Editable; +import android.util.Log; +import android.widget.Button; +import android.widget.EditText; + +import com.loopeer.codereader.R; +import com.loopeer.codereader.api.ServiceFactory; +import com.loopeer.codereader.api.service.GithubService; +import com.loopeer.codereader.model.Empty; +import com.loopeer.codereader.model.Token; +import com.loopeer.codereader.ui.view.Checker; +import com.loopeer.codereader.ui.view.LoginChecker; +import com.loopeer.codereader.ui.view.TextWatcherImpl; +import com.loopeer.codereader.utils.Base64; +import com.loopeer.codereader.utils.SnackbarUtils; + +import java.util.Arrays; +import java.util.List; + +import butterknife.BindView; +import butterknife.OnClick; +import retrofit2.Response; +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +public class LoginActivity extends BaseActivity implements Checker.CheckObserver { + + @BindView(R.id.edit_login_account) + EditText mEditLoginAccount; + @BindView(R.id.edit_login_password) + EditText mEditLoginPassword; + @BindView(R.id.btn_sign_in) + Button mBtnSignIn; + GithubService mGithubService; + + public final static String TOKEN_NOTE = "CodeReader APP Token"; + public final static String[] SCOPES = {"public_repo", "repo", "user", "gist"}; + private LoginChecker mLoginChecker; + private String mBase64Str; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + mGithubService = ServiceFactory.getGithubService(); + mLoginChecker = new LoginChecker(this); + + mEditLoginAccount.addTextChangedListener(new TextWatcherImpl() { + @Override + public void afterTextChanged(Editable editable) { + super.afterTextChanged(editable); + mLoginChecker.setUsername(editable.toString()); + } + }); + mEditLoginPassword.addTextChangedListener(new TextWatcherImpl() { + @Override + public void afterTextChanged(Editable editable) { + super.afterTextChanged(editable); + mLoginChecker.setPassword( + editable.toString()); + } + }); + } + + @OnClick(R.id.btn_sign_in) + public void onClick() { + final String username = mEditLoginAccount.getText().toString(); + final String password = mEditLoginPassword.getText().toString(); + mBase64Str = "Basic " + Base64.encode(username + ':' + password); + createToken(mBase64Str); + } + + private void createToken(String base64){ + + final Token token = new Token(); + token.setNote(TOKEN_NOTE); + token.setScopes(Arrays.asList(SCOPES)); + + registerSubscription( + mGithubService.createToken(token,mBase64Str) + .subscribeOn(Schedulers.io()) + .doOnSubscribe(() -> showProgressLoading("")) + .observeOn(AndroidSchedulers.mainThread()) + .doAfterTerminate(this::dismissProgressLoading) + .doOnNext(new Action1>() { + @Override + public void call(Response tokenResponse) { + if(tokenResponse.isSuccessful()){ + + SnackbarUtils.show(mBtnSignIn.getRootView(),"success"); + }else if(tokenResponse.code() == 401){ + SnackbarUtils.show(mBtnSignIn.getRootView(),R.string.login_auth_error); + }else if(tokenResponse.code() == 403){ + SnackbarUtils.show(mBtnSignIn.getRootView(),R.string.login_over_auth_error); + }else if(tokenResponse.code() == 422){ + findCertainTokenID(mBase64Str); + } + } + }) + .subscribe() + ); + } + + private void findCertainTokenID(String base64){ + mGithubService.listToken(base64) + .flatMap(new Func1>,Observable>>() { + @Override + public Observable> call(Response> listResponse) { + for(Token token : listResponse.body()){ + if(TOKEN_NOTE.equals(token.getNote())){ + return mGithubService.removeToken(base64,String.valueOf(token.getId())); + } + } + return Observable.empty(); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext(new Action1>() { + @Override + public void call(Response emptyResponse) { + if(emptyResponse.code() == 204){ + createToken(base64); + } + } + }) + .subscribe(); + } + + @Override + public void check(boolean b) { + mBtnSignIn.setEnabled(b); + } +} diff --git a/app/src/main/java/com/loopeer/codereader/ui/activity/MainActivity.java b/app/src/main/java/com/loopeer/codereader/ui/activity/MainActivity.java index 0808453..328496c 100644 --- a/app/src/main/java/com/loopeer/codereader/ui/activity/MainActivity.java +++ b/app/src/main/java/com/loopeer/codereader/ui/activity/MainActivity.java @@ -78,6 +78,7 @@ protected void onCreate(Bundle savedInstanceState) { public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_repo_add, menu); getMenuInflater().inflate(R.menu.menu_settings, menu); + getMenuInflater().inflate(R.menu.menu_github,menu); return super.onCreateOptionsMenu(menu); } @@ -90,6 +91,9 @@ public boolean onOptionsItemSelected(MenuItem item) { case R.id.action_repo_add: Navigator.startAddRepoActivity(this); break; + case R.id.action_github: + Navigator.startLoginActivity(this); + break; } return super.onOptionsItemSelected(item); } diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/LoginChecker.java b/app/src/main/java/com/loopeer/codereader/ui/view/LoginChecker.java new file mode 100644 index 0000000..163c712 --- /dev/null +++ b/app/src/main/java/com/loopeer/codereader/ui/view/LoginChecker.java @@ -0,0 +1,28 @@ +package com.loopeer.codereader.ui.view; + +import android.text.TextUtils; + +public class LoginChecker extends Checker { + + public String username; + public String password; + + public LoginChecker(CheckObserver checkObserver) { + super(checkObserver); + } + + public void setUsername(String username) { + this.username = username; + mCheckObserver.check(isEnable()); + } + + public void setPassword(String password) { + this.password = password; + mCheckObserver.check(isEnable()); + } + + @Override + boolean isEnable() { + return !TextUtils.isEmpty(username) && !TextUtils.isEmpty(password); + } +} diff --git a/app/src/main/java/com/loopeer/codereader/utils/Base64.java b/app/src/main/java/com/loopeer/codereader/utils/Base64.java new file mode 100755 index 0000000..9a66319 --- /dev/null +++ b/app/src/main/java/com/loopeer/codereader/utils/Base64.java @@ -0,0 +1,216 @@ +package com.loopeer.codereader.utils; + +import java.io.UnsupportedEncodingException; + +public class Base64 { + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte) '='; + + /** Preferred encoding. */ + private final static String PREFERRED_ENCODING = "US-ASCII"; + + /** The 64 valid Base64 values. */ + private final static byte[] _STANDARD_ALPHABET = { (byte) 'A', (byte) 'B', + (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', + (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', + (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', + (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', + (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', + (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', + (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', + (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', + (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', + (byte) '+', (byte) '/' }; + + /** Defeats instantiation. */ + private Base64() { + } + + /** + *

+ * Encodes up to three bytes of the array source and writes the + * resulting four Base64 bytes to destination. The source and + * destination arrays can be manipulated anywhere along their length by + * specifying srcOffset and destOffset. This method + * does not check to make sure your arrays are large enough to accomodate + * srcOffset + 3 for the source array or + * destOffset + 4 for the destination array. The + * actual number of significant bytes in your array is given by + * numSigBytes. + *

+ *

+ * This is the lowest level of the encoding methods with all possible + * parameters. + *

+ * + * @param source + * the array to convert + * @param srcOffset + * the index where conversion begins + * @param numSigBytes + * the number of significant bytes in your array + * @param destination + * the array to hold the conversion + * @param destOffset + * the index where output will be put + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4(byte[] source, int srcOffset, + int numSigBytes, byte[] destination, int destOffset) { + + byte[] ALPHABET = _STANDARD_ALPHABET; + + int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) + | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) + | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); + + switch (numSigBytes) { + case 3: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; + return destination; + + case 2: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + + case 1: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = EQUALS_SIGN; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + + default: + return destination; + } + } + + /** + * Encode string as a byte array in Base64 annotation. + * + * @param string + * @return The Base64-encoded data as a string + */ + public static String encode(String string) { + byte[] bytes; + try { + bytes = string.getBytes(PREFERRED_ENCODING); + } catch (UnsupportedEncodingException e) { + bytes = string.getBytes(); + } + return encodeBytes(bytes); + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source + * The data to convert + * @return The Base64-encoded data as a String + * @throws NullPointerException + * if source array is null + * @throws IllegalArgumentException + * if source array, offset, or length are invalid + * @since 2.0 + */ + public static String encodeBytes(byte[] source) { + return encodeBytes(source, 0, source.length); + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source + * The data to convert + * @param off + * Offset in array where conversion should begin + * @param len + * Length of data to convert + * @return The Base64-encoded data as a String + * @throws NullPointerException + * if source array is null + * @throws IllegalArgumentException + * if source array, offset, or length are invalid + * @since 2.0 + */ + public static String encodeBytes(byte[] source, int off, int len) { + byte[] encoded = encodeBytesToBytes(source, off, len); + try { + return new String(encoded, PREFERRED_ENCODING); + } catch (UnsupportedEncodingException uue) { + return new String(encoded); + } + } + + /** + * Similar to {@link #encodeBytes(byte[], int, int)} but returns a byte + * array instead of instantiating a String. This is more efficient if you're + * working with I/O streams and have large data sets to encode. + * + * + * @param source + * The data to convert + * @param off + * Offset in array where conversion should begin + * @param len + * Length of data to convert + * @return The Base64-encoded data as a String if there is an error + * @throws NullPointerException + * if source array is null + * @throws IllegalArgumentException + * if source array, offset, or length are invalid + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes(byte[] source, int off, int len) { + + if (source == null) + throw new NullPointerException("Cannot serialize a null array."); + + if (off < 0) + throw new IllegalArgumentException("Cannot have negative offset: " + + off); + + if (len < 0) + throw new IllegalArgumentException("Cannot have length offset: " + len); + + if (off + len > source.length) + throw new IllegalArgumentException( + String + .format( + "Cannot have offset of %d and length of %d with array of length %d", + off, len, source.length)); + + // Bytes needed for actual encoding + int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); + + byte[] outBuff = new byte[encLen]; + + int d = 0; + int e = 0; + int len2 = len - 2; + for (; d < len2; d += 3, e += 4) + encode3to4(source, d + off, 3, outBuff, e); + + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e); + e += 4; + } + + if (e <= outBuff.length - 1) { + byte[] finalOut = new byte[e]; + System.arraycopy(outBuff, 0, finalOut, 0, e); + return finalOut; + } else + return outBuff; + } + } + diff --git a/app/src/main/java/com/loopeer/codereader/utils/SnackbarUtils.java b/app/src/main/java/com/loopeer/codereader/utils/SnackbarUtils.java new file mode 100644 index 0000000..ebc93fb --- /dev/null +++ b/app/src/main/java/com/loopeer/codereader/utils/SnackbarUtils.java @@ -0,0 +1,18 @@ +package com.loopeer.codereader.utils; + +import android.support.annotation.StringRes; +import android.support.design.widget.Snackbar; +import android.view.View; + +import com.loopeer.codereader.R; + +public class SnackbarUtils { + + public static void show(View view,String s){ + Snackbar.make(view,s,Snackbar.LENGTH_SHORT).show(); + } + + public static void show(View view, @StringRes int id){ + Snackbar.make(view,id,Snackbar.LENGTH_SHORT).show(); + } +} diff --git a/app/src/main/res/drawable-xxhdpi/ic_menu_github.png b/app/src/main/res/drawable-xxhdpi/ic_menu_github.png new file mode 100644 index 0000000000000000000000000000000000000000..88b856ebd4dd5fb933a27cbfe4d8b72fa5571997 GIT binary patch literal 1471 zcmV;w1wi_VP)LB3c%hA$Wk=XXcAB-^eI{f{po3M6flh9!Es5rLh-qFNe|rd?)+QLh?RfJ6jR10&q(fXcz^6^|l7!4Lxu+;;2ZfSHd=7g-AE zS0nbux^LK|L+j-JJU5_cpjIgkMe61P^o-$j(006GfEkd8z}}oHVtO5-=2s=+TzQS< z*>L@DJ!y&wP%z5BwaA#6AMPh2NJ0S=LF4Pnjpr^uM{dT0YP#! zk9En+$j}#th~V#FAm3RRWtfNj3+cr|!Wv%WAH(&Znb{P(sep{Cg*6>BGbAQD!hO1! z1OXgUf__?nlEz(-K1d)2{zP<+9{@v*_HqHBhya=T7Jwjx3BkT?{u4%D}kAxsodfqFF|Js=Xo1VHjc^GP*J0YVUkLYQ@^-oSdJG3Gfv z86KJRz$rio;H;6a>Iuf!wC5fnqDH`Im7-)hBn8N0=$Z%+hX6&yqXkJj^aM~Sz@s2$ z8eM{Fn!LnGPCI(T%$u?0#Q-6>{At`vGsZb0K}e4f6PZTpMZZ`;!K0Z?g$Ix)muXgl zynu)(>WO0#^uGd%y80Q=KMQ3Bgre6slVu}HT7Tp9lT8biOslgYj}7v8F>ElK4Npcu zxv{gBH7TH%5GYOxsEI5-5(jxse{Y#NT2>w-)wpBk^;M}-?77}-Ahh&FOCDn+t5{=G zYUU{1cYNK+swWo0Lz?ks1=3Sg?(ljtJfygTVodcE5s&S8cyun|PN^75t(FtY;oequ zvJq-PhEYvM#xQk1)M)(>S{JU!nCO|=oa{Yf#)F#wpx(pcx8ohzVp9VPE z>onDJ**`O&04XKrOUcCwIyd^TV0X5?FUbW+u}6;v+al*Bb1DH9IR`hO04a5L4=;vq zBha2Z`ltY8?rI$_x)czMHX`I)vt;bUHiY(CJ8=?hA)q43=@wB0s23c18U)-PWJt;h z2sQ^T!{&%R_GYR81$Fau!n#~+1)w5>JPDux2{{s^N$V+E1|W3d!w$CbN#-DDLKuMT zKGnE1B_MR+15A4#^$sAxfCCa#ZwP7%K(~-y>$ewP*II3ZQV&eji`4o{ZUZ9H4=)zJ z^zm-oI)5@i1F3?ITB{ZR03G*2+>YYOWdYJJ{PB&c-=x%JI}l(#nnngP`*_$)yR ZzrViBc4vX3ye0qu002ovPDHLkV1i6sh#>#~ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_pwd_unvisible.png b/app/src/main/res/drawable-xxhdpi/ic_pwd_unvisible.png new file mode 100644 index 0000000000000000000000000000000000000000..df129664e540b3b402ee52b2f220acde7f7d89f4 GIT binary patch literal 1288 zcmV+j1^4=iP)P=l5Xb*TB*(1cIl*L35c>oJn<(9zkc}a$@tk1n6C^poaD&*ZKsIqUJSiLS35GdA zoD+ndnHiu))ihQc`lG68Vqv-^OP2ilb=B|v>*G~I*zt#U+<%k+N(JmVfieVkTmWSf zEJL6SfgLAc4uOwfz8qpYJv{cj^M}CB1VEhU$)pdW0|I`32oTf&qRuZ){vkln190wy zf%nU@w?O#R?;rekbMuUeo&u1c;9;U+Wi}hh@5jD>^&>+|4M2?gXoMgR|2rPPWS|D1 zMiHmeoMd1?%tNkJ2CWw_K5r6;34j$s5-^KH3|td%J_9aZd)~F>UCmlEfct=O8!Ev0 z5Q5Cf!x_=5b;BaJ0>p-{b8i5=jXR0B z>Ja9H8kjGezJIxRPm<+$JU%4m27m?;{j54&=1~Y?pjscZC^vwPbMFZ_N&B#+L`L`N zIww$10X)$-miIfdcP;=L=NE?OHBK6@*>rkNz_U$4FaxkSzb*s`jel-#hT4#zM~{w< zpFbaJfJJQ6FJGsw$0^~1b5ASGa;f?bqRXb|T`XG@wBXa}Un(o-IpN5X7}W|e8IoXa zY+{@{C`s&zWcIykwZ2^E!<--qH}jD$vgLX8%!T0sHGqzDeVVtls)w1+i0IGd#d#ov zE%kTRhl2{qXD#1vYu2j(Sf$Uv=a%O+EO&=cVRIz_@0y->r@VHYyCmSH#wlr*w4tB^ z&~fgo_97z&Mo%CY1ZHmf{!dY7t%m^MqUm|5Wm;7Rb9@*xp#IwPZkv-%uh%A`KP=%e z0~*miJ~&sa0DYAL=BF^)t5$!>x;JDhz$CrUx@7=qNh~QR$X3ykW(F{~WhIni4k4Xs z(&?qWWdRV%Fmt??2Y{vt%Tz#6tAVd-=>aPt(CzgaAo`THN0zZx0L=WY<@>i&r=)LZ z0+1KG&PlWKn*qX#^t#T;9X_?UPlI4pViwSaFDd|`3Z%tUT~4H-Y5M+A6qrT@Y2nau zZm7K%fcdKF`>LKr1wahz^m?apjZKm&mh(WkM{G5)R0qu=SndRB06{49dIJ#sstJXp zmQ<9$fPhH7^JjL92}W4DKvf?Q{f;#h(u|S47NhYB^8tt?&)f;I91v8^Pt+%q0_4S| zmNioh+GLcpuNDFWf44mEFw>x=1!e$wAjsHEGsMgbL7acQzhBGh`r_c^0+0tnINui# zD|h~dVG5f$&2BSPV*Ay9o$e1t91@jQ>d^3;xytNP`e} z1AUMH5+Il_LTh(|S#&r3_eeP8a(?RphyyY!j8+RP^sUSRRwvWemjH^Sxs7|33fRU| yE7Imt0Y%c>#=S}fY~!gFX>+N7B57{pUjG9KR=Qv8-FuG!00001k literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_pwd_visible.png b/app/src/main/res/drawable-xxhdpi/ic_pwd_visible.png new file mode 100644 index 0000000000000000000000000000000000000000..bf9197bbdbfc5d8ef87c137ce6dd61c76b892925 GIT binary patch literal 1272 zcmV z3gjuU<_P2|u;v8FyTFU5z*!KyiCIU8RsI`^y7B+^eE;etS-DgI&O4pW`}_MhI2^L0 zRY0sroFjlP67{H1=+?KlFC}x*j#&x-XWze9hL4XQk!S}HH`5vy&Lg@3fYTx3$$QW1 zFSOQ_$(aHWIev^B?Ik?S3<5aJ5{OIyV58%ywe+LA{_*FD*-)tDU%JqrWC zQKyH8&cakzx-gOu0Z* z0mK~u;NKsjHHmHz@zb8?or=b1Sp!aG>l zej+bJWq5aYK2s+UT@*{D!z5$c#&D7QU~?waO#uB3*WHq`P7FXh2+pG_EwN)zblvi( z5fOmgF6YZ(qF%97s*TQ#QsG*%@soI@le+JVG>Hg+Z^l#$03ERb0GD;o<97D^sG>N< zfp!pd0rB}DWwS+^3L*en#&k6DHd&Fi5oG6zuKVXG3);qT3;k?!Jburzx$b#2+juJg z+UWC!N%UJm&_Kjvn=zyBlWU~3<8Rx!!3sdj7*=lYG{&RjY>ifQZS3hD+5GguVkagcLj~d+4GtnCT5NHXtwUQKwdi%b=m2f~_2#I$0 zeg9%?zA@JbFb2vT+b49$tg#{hc(S&VCn?NpNSKt&eV(Vz|0w`23_$3BLX6Dn!+f3~ z04}V5jT*(LMgnL|)A8*f_+eF$(PA`3gw12Qm;q5FGCQ4dTEQ<$_hmC1LDbA5D2kR-`+lv6x?`BI|z;ym#j07-nKnc z0T4zfR(x)mg;%e3v^P#MFsT8EYXUx9a=x}9{rsNjh7fs9 zpAf@qZ6w-^E=Tz5k^Dmn(N(cj+R@~ECMhtwNO;L{dwZmn%VNN04Ah+YGwTBL7K*+P zG&N`TR3H#t4G|Bt#V!*aATgLL_Yk0=N(;%tMEupbnN*09sV~BSW060jn5QjZZlPG-$2j*qi~Y7*&l= iIRi9kt>W0VXTblO-z;B3;c4yw0000 + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..a40ab1d --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + +