@@ -1,3 +0,0 @@ | |||
# Default ignored files | |||
/shelf/ | |||
/workspace.xml |
@@ -1 +0,0 @@ | |||
ImAndroid |
@@ -1,7 +1,6 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project version="4"> | |||
<component name="VcsDirectoryMappings"> | |||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" /> | |||
<mapping directory="$PROJECT_DIR$" vcs="Git" /> | |||
<mapping directory="" vcs="Git" /> | |||
</component> | |||
</project> |
@@ -7,7 +7,7 @@ android { | |||
compileSdk 33 | |||
defaultConfig { | |||
minSdk 24 | |||
minSdk 26 | |||
targetSdk 33 | |||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | |||
@@ -1,19 +1,22 @@ | |||
plugins { | |||
id 'com.android.library' | |||
id "io.sentry.android.gradle" version "3.4.2" | |||
} | |||
def versionCode = 1 | |||
def versionName = "0.0.1.011" | |||
android { | |||
namespace 'cn.org.bjca.trust.android.lib.im' | |||
compileSdk 33 | |||
defaultConfig { | |||
minSdk 24 | |||
minSdk 26 | |||
targetSdk 33 | |||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | |||
consumerProguardFiles "consumer-rules.pro" | |||
versionCode 1 | |||
versionName '1.0.0' | |||
buildConfigField("String", "versionName", "\"${versionName}\"") | |||
} | |||
buildTypes { | |||
@@ -31,9 +34,25 @@ android { | |||
dependencies { | |||
api project(path: ':SzyxBaseSdk') | |||
implementation 'androidx.appcompat:appcompat:1.4.1' | |||
implementation 'com.google.android.material:material:1.5.0' | |||
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.room:room-runtime:2.5.0") | |||
annotationProcessor ("androidx.room:room-compiler:2.5.0") | |||
// 网络相关 | |||
//Rxjava | |||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' | |||
//Retrofit | |||
implementation 'com.squareup.retrofit2:retrofit:2.9.0' | |||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' | |||
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' | |||
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1' | |||
} |
@@ -1,4 +1,32 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> | |||
<application> | |||
<!-- Required: set your sentry.io project identifier (DSN) --> | |||
<meta-data | |||
android:name="io.sentry.dsn" | |||
android:value="https://e0308587cb2041d4909b97f3e4549d8d@sentry.51trust.net/9" /> | |||
<!-- enable automatic breadcrumbs for user interactions (clicks, swipes, scrolls) --> | |||
<meta-data | |||
android:name="io.sentry.traces.user-interaction.enable" | |||
android:value="true" /> | |||
<!-- enable screenshot for crashes --> | |||
<meta-data | |||
android:name="io.sentry.attach-screenshot" | |||
android:value="true" /> | |||
<!-- enable view hierarchy for crashes --> | |||
<meta-data | |||
android:name="io.sentry.attach-view-hierarchy" | |||
android:value="true" /> | |||
<!-- enable the performance API by setting a sample-rate, adjust in production env --> | |||
<meta-data | |||
android:name="io.sentry.traces.sample-rate" | |||
android:value="1.0" /> | |||
<!-- enable profiling when starting transactions, adjust in production env --> | |||
<meta-data | |||
android:name="io.sentry.traces.profiling.sample-rate" | |||
android:value="1.0" /> | |||
</application> | |||
</manifest> |
@@ -0,0 +1,25 @@ | |||
package cn.org.bjca.trust.android.lib.im; | |||
import android.content.Context; | |||
import androidx.room.Room; | |||
import cn.org.bjca.trust.android.lib.im.db.ImDatabase; | |||
public class SZYXDbHelper { | |||
private static ImDatabase dataBase; | |||
public static ImDatabase get() { | |||
return dataBase; | |||
} | |||
public static void get(Context context) { | |||
if (null == dataBase) { | |||
dataBase = Room.databaseBuilder(context, ImDatabase.class, "szyx-im-db") | |||
// .addMigrations(MIGRATION_1_2) | |||
.allowMainThreadQueries() //允许在主线程 操作db | |||
.build(); | |||
} | |||
} | |||
} |
@@ -1,12 +1,12 @@ | |||
package cn.org.bjca.trust.android.lib.im; | |||
import cn.org.bjca.trust.android.lib.im.kit.SdkInterface; | |||
import cn.org.bjca.trust.android.lib.im.manager.PushSdkManager; | |||
import cn.org.bjca.trust.android.lib.im.manager.SZYXImManager; | |||
public class SzyxPush { | |||
private static final class SdkInterfaceHolder { | |||
static final SdkInterface instance = new PushSdkManager(); | |||
static final SdkInterface instance = new SZYXImManager(); | |||
} | |||
public static SdkInterface getInstance() { | |||
@@ -0,0 +1,15 @@ | |||
package cn.org.bjca.trust.android.lib.im.cfg; | |||
public class Constant { | |||
public static final String BaseUrl = "https://221n3i2201.goho.co"; | |||
private static String sdkAppID; | |||
public static void setSdkAppID(String sdkAppID) { | |||
Constant.sdkAppID = sdkAppID; | |||
} | |||
public static String getSdkAppID() { | |||
return sdkAppID; | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
package cn.org.bjca.trust.android.lib.im.common; | |||
import android.util.Log; | |||
import java.util.Arrays; | |||
public class CommonHelper { | |||
public static String anyToString(Object obj) { | |||
if (null == obj) return ""; | |||
else if (obj.getClass().getTypeName().equals("java.lang.String[]")) { | |||
return Arrays.toString((String[]) obj); | |||
} else return obj.toString(); | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
package cn.org.bjca.trust.android.lib.im.common; | |||
import android.os.Build; | |||
import android.util.Log; | |||
import java.lang.reflect.Field; | |||
import java.util.List; | |||
import cn.org.bjca.trust.android.lib.im.SZYXDbHelper; | |||
import cn.org.bjca.trust.android.lib.im.db.device.DeviceEntity; | |||
public class DeviceHelper { | |||
public static DeviceEntity getDevice() { | |||
List<DeviceEntity> entityList = SZYXDbHelper.get().deviceDao().getAll(); | |||
if (entityList.size() == 0) { | |||
DeviceEntity device = new DeviceEntity(); | |||
Field[] fields = Build.class.getDeclaredFields(); | |||
for (Field field : fields) { | |||
try { | |||
switch (field.getName()) { | |||
case "MANUFACTURER": | |||
device.setManufacturer(CommonHelper.anyToString(field.get(null))); | |||
break; | |||
case "BRAND": | |||
device.setBrand(CommonHelper.anyToString(field.get(null))); | |||
break; | |||
case "MODEL": | |||
device.setModel(CommonHelper.anyToString(field.get(null))); | |||
break; | |||
case "CPU_ABI": | |||
device.setCpuAbi(CommonHelper.anyToString(field.get(null))); | |||
break; | |||
case "FINGERPRINT": | |||
device.setFingerprint(CommonHelper.anyToString(field.get(null))); | |||
device.setDeviceId(CommonHelper.anyToString(field.get(null))); | |||
break; | |||
case "SUPPORTED_32_BIT_ABIS": | |||
device.setSupported32BitAbis(CommonHelper.anyToString(field.get(null))); | |||
break; | |||
case "SUPPORTED_64_BIT_ABIS": | |||
device.setSupported64BitAbis(CommonHelper.anyToString(field.get(null))); | |||
break; | |||
case "SUPPORTED_ABIS": | |||
device.setSupportedAbis(CommonHelper.anyToString(field.get(null))); | |||
break; | |||
} | |||
} catch (Exception e) { | |||
} | |||
} | |||
SZYXDbHelper.get().deviceDao().insertAll(device); | |||
return device; | |||
} else return entityList.get(0); | |||
} | |||
} |
@@ -0,0 +1,16 @@ | |||
package cn.org.bjca.trust.android.lib.im.db; | |||
import androidx.room.PrimaryKey; | |||
public class BaseEntity { | |||
@PrimaryKey(autoGenerate = true) | |||
private int _uid; | |||
public int get_uid() { | |||
return _uid; | |||
} | |||
public void set_uid(int _uid) { | |||
this._uid = _uid; | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
package cn.org.bjca.trust.android.lib.im.db; | |||
import androidx.room.Database; | |||
import androidx.room.RoomDatabase; | |||
import cn.org.bjca.trust.android.lib.im.db.device.DeviceDao; | |||
import cn.org.bjca.trust.android.lib.im.db.device.DeviceEntity; | |||
@Database(entities = {DeviceEntity.class}, version = 1, exportSchema = false) | |||
public abstract class ImDatabase extends RoomDatabase { | |||
public abstract DeviceDao deviceDao(); | |||
} |
@@ -0,0 +1,25 @@ | |||
package cn.org.bjca.trust.android.lib.im.db.device; | |||
import androidx.room.Dao; | |||
import androidx.room.Delete; | |||
import androidx.room.Insert; | |||
import androidx.room.Query; | |||
import androidx.room.Update; | |||
import java.util.List; | |||
@Dao | |||
public interface DeviceDao { | |||
@Query("SELECT * FROM device") | |||
List<DeviceEntity> getAll(); | |||
@Insert | |||
void insertAll(DeviceEntity... devices); | |||
@Update | |||
void update(DeviceEntity device); | |||
@Delete | |||
void delete(DeviceEntity device); | |||
} |
@@ -0,0 +1,125 @@ | |||
package cn.org.bjca.trust.android.lib.im.db.device; | |||
import androidx.annotation.NonNull; | |||
import androidx.room.ColumnInfo; | |||
import androidx.room.Entity; | |||
import cn.org.bjca.trust.android.lib.im.db.BaseEntity; | |||
@Entity(tableName = "device") | |||
public class DeviceEntity extends BaseEntity { | |||
@ColumnInfo(name = "device_id") | |||
private String deviceId; | |||
// 厂商 MANUFACTURER | |||
@ColumnInfo(name = "manufacturer") | |||
private String manufacturer; | |||
// 品牌 BRAND | |||
@ColumnInfo(name = "brand") | |||
private String brand; | |||
// 型号 MODEL | |||
@ColumnInfo(name = "model") | |||
private String model; | |||
// cpu CPU_ABI | |||
@ColumnInfo(name = "cpu_abi") | |||
private String cpuAbi; | |||
// 指纹 FINGERPRINT | |||
@ColumnInfo(name = "fingerprint") | |||
private String fingerprint; | |||
// SUPPORTED_32_BIT_ABIS | |||
@ColumnInfo(name = "supported_32_bit_abis") | |||
private String supported32BitAbis; | |||
// SUPPORTED_64_BIT_ABIS | |||
@ColumnInfo(name = "supported_64_bit_abis") | |||
private String supported64BitAbis; | |||
// SUPPORTED_ABIS | |||
@ColumnInfo(name = "supported_abis") | |||
private String supportedAbis; | |||
public String getDeviceId() { | |||
return deviceId; | |||
} | |||
public void setDeviceId(String deviceId) { | |||
this.deviceId = deviceId; | |||
} | |||
public String getManufacturer() { | |||
return manufacturer; | |||
} | |||
public void setManufacturer(String manufacturer) { | |||
this.manufacturer = manufacturer; | |||
} | |||
public String getBrand() { | |||
return brand; | |||
} | |||
public void setBrand(String brand) { | |||
this.brand = brand; | |||
} | |||
public String getModel() { | |||
return model; | |||
} | |||
public void setModel(String model) { | |||
this.model = model; | |||
} | |||
public String getCpuAbi() { | |||
return cpuAbi; | |||
} | |||
public void setCpuAbi(String cpuAbi) { | |||
this.cpuAbi = cpuAbi; | |||
} | |||
public String getFingerprint() { | |||
return fingerprint; | |||
} | |||
public void setFingerprint(String fingerprint) { | |||
this.fingerprint = fingerprint; | |||
} | |||
public String getSupported32BitAbis() { | |||
return supported32BitAbis; | |||
} | |||
public void setSupported32BitAbis(String supported32BitAbis) { | |||
this.supported32BitAbis = supported32BitAbis; | |||
} | |||
public String getSupported64BitAbis() { | |||
return supported64BitAbis; | |||
} | |||
public void setSupported64BitAbis(String supported64BitAbis) { | |||
this.supported64BitAbis = supported64BitAbis; | |||
} | |||
public String getSupportedAbis() { | |||
return supportedAbis; | |||
} | |||
public void setSupportedAbis(String supportedAbis) { | |||
this.supportedAbis = supportedAbis; | |||
} | |||
@NonNull | |||
@Override | |||
public String toString() { | |||
return "DeviceEntity{" + | |||
"deviceId='" + deviceId + '\'' + | |||
", manufacturer='" + manufacturer + '\'' + | |||
", brand='" + brand + '\'' + | |||
", model='" + model + '\'' + | |||
", cpuAbi='" + cpuAbi + '\'' + | |||
", fingerprint='" + fingerprint + '\'' + | |||
", supported32BitAbis='" + supported32BitAbis + '\'' + | |||
", supported64BitAbis='" + supported64BitAbis + '\'' + | |||
", supportedAbis='" + supportedAbis + '\'' + | |||
'}'; | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
package cn.org.bjca.trust.android.lib.im.http; | |||
import androidx.annotation.NonNull; | |||
import java.io.IOException; | |||
import cn.org.bjca.trust.android.lib.im.BuildConfig; | |||
import cn.org.bjca.trust.android.lib.im.cfg.Constant; | |||
import okhttp3.Interceptor; | |||
import okhttp3.Response; | |||
public class HeaderInterceptor implements Interceptor { | |||
@NonNull | |||
@Override | |||
public Response intercept(@NonNull Chain chain) throws IOException { | |||
return chain.proceed(chain.request().newBuilder() | |||
.header("AppID", Constant.getSdkAppID()) | |||
.addHeader("Version", BuildConfig.versionName) | |||
.addHeader("OsType", "1") | |||
.build()); | |||
} | |||
} |
@@ -0,0 +1,47 @@ | |||
package cn.org.bjca.trust.android.lib.im.http; | |||
import java.util.concurrent.TimeUnit; | |||
import cn.org.bjca.trust.android.lib.im.cfg.Constant; | |||
import okhttp3.OkHttpClient; | |||
import okhttp3.logging.HttpLoggingInterceptor; | |||
import retrofit2.Retrofit; | |||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; | |||
import retrofit2.converter.gson.GsonConverterFactory; | |||
public class HttpManage { | |||
private static Retrofit retrofit; | |||
private static OkHttpClient okHttpClient; | |||
public static <T> T getApi(final Class<T> service) { | |||
if (null == okHttpClient) { | |||
OkHttpClient.Builder builder = new OkHttpClient.Builder(); | |||
builder.hostnameVerifier((s, sslSession) -> true); | |||
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); | |||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); | |||
okHttpClient = builder | |||
.connectTimeout(15, TimeUnit.SECONDS) | |||
.addInterceptor(loggingInterceptor) | |||
.addInterceptor(new HeaderInterceptor()) | |||
.build(); | |||
} | |||
if (null == retrofit) { | |||
retrofit = new Retrofit.Builder() | |||
.baseUrl(Constant.BaseUrl) | |||
.client(okHttpClient) | |||
.addConverterFactory(GsonConverterFactory.create()) | |||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) | |||
.build(); | |||
} | |||
return retrofit.create(service); | |||
} | |||
} |
@@ -0,0 +1,37 @@ | |||
package cn.org.bjca.trust.android.lib.im.http; | |||
public class HttpResult<T> { | |||
/** | |||
* "code": 200 | |||
* "message": "success" | |||
* data : | |||
*/ | |||
private int code; | |||
private String msg; | |||
private T data; | |||
public int getCode() { | |||
return code; | |||
} | |||
public void setCode(int code) { | |||
this.code = code; | |||
} | |||
public String getMsg() { | |||
return msg; | |||
} | |||
public void setMsg(String msg) { | |||
this.msg = msg; | |||
} | |||
public T getData() { | |||
return data; | |||
} | |||
public void setData(T data) { | |||
this.data = data; | |||
} | |||
} |
@@ -1,60 +0,0 @@ | |||
package cn.org.bjca.trust.android.lib.im.manager; | |||
import android.content.Context; | |||
import cn.org.bjca.trust.android.lib.im.kit.IMSDKCallback; | |||
import cn.org.bjca.trust.android.lib.im.kit.IMSDKListener; | |||
import cn.org.bjca.trust.android.lib.im.kit.MsgListener; | |||
import cn.org.bjca.trust.android.lib.im.kit.SdkInterface; | |||
public class PushSdkManager implements SdkInterface { | |||
@Override | |||
public void addIMSDKListener(IMSDKListener listener) { | |||
} | |||
@Override | |||
public void removeIMSDKListener(IMSDKListener listener) { | |||
} | |||
@Override | |||
public void init(Context context, String sdkAppID, IMSDKCallback callback) { | |||
} | |||
@Override | |||
public void login(String userID, String userSig, IMSDKCallback callback) { | |||
} | |||
@Override | |||
public void logout(IMSDKCallback callback) { | |||
} | |||
@Override | |||
public void addMsgListener(MsgListener listener) { | |||
} | |||
@Override | |||
public void removeMsgListener(MsgListener listener) { | |||
} | |||
@Override | |||
public void sendMsgForTextToC(String toUserId, String text, IMSDKCallback callback) { | |||
} | |||
@Override | |||
public void sendMsgForTextToG(String toGroupId, String text, IMSDKCallback callback) { | |||
} | |||
@Override | |||
public String getVersion() { | |||
return null; | |||
} | |||
} |
@@ -0,0 +1,88 @@ | |||
package cn.org.bjca.trust.android.lib.im.manager; | |||
import android.content.Context; | |||
import android.util.Log; | |||
import cn.org.bjca.trust.android.lib.im.BuildConfig; | |||
import cn.org.bjca.trust.android.lib.im.SZYXDbHelper; | |||
import cn.org.bjca.trust.android.lib.im.cfg.Constant; | |||
import cn.org.bjca.trust.android.lib.im.common.DeviceHelper; | |||
import cn.org.bjca.trust.android.lib.im.http.HttpManage; | |||
import cn.org.bjca.trust.android.lib.im.kit.IMSDKCallback; | |||
import cn.org.bjca.trust.android.lib.im.kit.IMSDKListener; | |||
import cn.org.bjca.trust.android.lib.im.kit.MsgListener; | |||
import cn.org.bjca.trust.android.lib.im.kit.SdkInterface; | |||
import cn.org.bjca.trust.android.lib.im.repository.Service; | |||
import cn.org.bjca.trust.android.lib.im.repository.data.LoginData; | |||
import io.reactivex.android.schedulers.AndroidSchedulers; | |||
import io.reactivex.disposables.Disposable; | |||
import io.reactivex.schedulers.Schedulers; | |||
public class SZYXImManager implements SdkInterface { | |||
private IMSDKListener imsdkListener; | |||
@Override | |||
public void addIMSDKListener(IMSDKListener listener) { | |||
this.imsdkListener = listener; | |||
} | |||
@Override | |||
public void removeIMSDKListener(IMSDKListener listener) { | |||
this.imsdkListener = null; | |||
} | |||
@Override | |||
public void init(Context context, String sdkAppID, IMSDKCallback callback) { | |||
Constant.setSdkAppID(sdkAppID); | |||
SZYXDbHelper.get(context); | |||
DeviceHelper.getDevice(); | |||
} | |||
@Override | |||
public void login(String userID, String userSig, IMSDKCallback callback) { | |||
Disposable d = HttpManage.getApi(Service.class) | |||
.login(new LoginData(userID, userSig, DeviceHelper.getDevice())) | |||
.subscribeOn(Schedulers.io()) | |||
.observeOn(AndroidSchedulers.mainThread()) | |||
.subscribe(httpResult -> { | |||
if (httpResult.getCode() == 200) { | |||
if (null != callback) callback.success(); | |||
if (null != imsdkListener) imsdkListener.onConnecting(); | |||
} else if (null != callback) callback.failed(1001, httpResult.getMsg()); | |||
}, throwable -> { | |||
if (null != callback) callback.failed(1001, throwable.getMessage()); | |||
}); | |||
} | |||
@Override | |||
public void logout(IMSDKCallback callback) { | |||
} | |||
@Override | |||
public void addMsgListener(MsgListener listener) { | |||
} | |||
@Override | |||
public void removeMsgListener(MsgListener listener) { | |||
} | |||
@Override | |||
public void sendMsgForTextToC(String toUserId, String text, IMSDKCallback callback) { | |||
} | |||
@Override | |||
public void sendMsgForTextToG(String toGroupId, String text, IMSDKCallback callback) { | |||
} | |||
@Override | |||
public String getVersion() { | |||
return BuildConfig.versionName; | |||
} | |||
} |
@@ -0,0 +1,20 @@ | |||
package cn.org.bjca.trust.android.lib.im.repository; | |||
import cn.org.bjca.trust.android.lib.im.bean.UserInfo; | |||
import cn.org.bjca.trust.android.lib.im.http.HttpResult; | |||
import cn.org.bjca.trust.android.lib.im.repository.bean.LoginBean; | |||
import cn.org.bjca.trust.android.lib.im.repository.data.LoginData; | |||
import io.reactivex.Observable; | |||
import retrofit2.http.Body; | |||
import retrofit2.http.GET; | |||
import retrofit2.http.POST; | |||
import retrofit2.http.Path; | |||
public interface Service { | |||
@GET("hello/{id}") | |||
Observable<HttpResult<String>> test(@Path("id") String id); | |||
@POST("user/v1/login") | |||
Observable<HttpResult<LoginBean>> login(@Body LoginData loginData); | |||
} |
@@ -0,0 +1,8 @@ | |||
package cn.org.bjca.trust.android.lib.im.repository.bean; | |||
public class LoginBean { | |||
private String host; | |||
private String port; | |||
private String clientId; | |||
private String sign; | |||
} |
@@ -0,0 +1,42 @@ | |||
package cn.org.bjca.trust.android.lib.im.repository.data; | |||
import cn.org.bjca.trust.android.lib.im.db.device.DeviceEntity; | |||
public class LoginData { | |||
private String userId; | |||
private String userSig; | |||
private DeviceEntity device; | |||
public LoginData() { | |||
} | |||
public LoginData(String userId, String userSig, DeviceEntity device) { | |||
this.userId = userId; | |||
this.userSig = userSig; | |||
this.device = device; | |||
} | |||
public String getUserId() { | |||
return userId; | |||
} | |||
public void setUserId(String userId) { | |||
this.userId = userId; | |||
} | |||
public String getUserSig() { | |||
return userSig; | |||
} | |||
public void setUserSig(String userSig) { | |||
this.userSig = userSig; | |||
} | |||
public DeviceEntity getDevice() { | |||
return device; | |||
} | |||
public void setDevice(DeviceEntity device) { | |||
this.device = device; | |||
} | |||
} |
@@ -8,7 +8,7 @@ android { | |||
defaultConfig { | |||
applicationId "cn.org.bjca.trust.android.imdemo" | |||
minSdk 24 | |||
minSdk 26 | |||
targetSdk 33 | |||
versionCode 1 | |||
versionName '1.0.0' | |||
@@ -26,6 +26,9 @@ android { | |||
sourceCompatibility JavaVersion.VERSION_1_8 | |||
targetCompatibility JavaVersion.VERSION_1_8 | |||
} | |||
buildFeatures { | |||
viewBinding true | |||
} | |||
} | |||
dependencies { | |||
@@ -34,6 +37,9 @@ dependencies { | |||
implementation 'androidx.appcompat:appcompat:1.4.1' | |||
implementation 'com.google.android.material:material:1.5.0' | |||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3' | |||
implementation 'androidx.annotation:annotation:1.3.0' | |||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1' | |||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' | |||
testImplementation 'junit:junit:4.13.2' | |||
androidTestImplementation 'androidx.test.ext:junit:1.1.3' | |||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' |
@@ -1,6 +1,7 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |||
xmlns:tools="http://schemas.android.com/tools"> | |||
<uses-permission android:name="android.permission.INTERNET" /> | |||
<application | |||
android:name=".MyApplication" | |||
@@ -13,14 +14,18 @@ | |||
android:theme="@style/Theme.ImAndroid" | |||
tools:targetApi="31"> | |||
<activity | |||
android:name=".MainActivity" | |||
android:exported="true"> | |||
android:name=".ui.login.LoginActivity" | |||
android:exported="true" | |||
android:label="@string/title_activity_login"> | |||
<intent-filter> | |||
<action android:name="android.intent.action.MAIN" /> | |||
<category android:name="android.intent.category.LAUNCHER" /> | |||
</intent-filter> | |||
</activity> | |||
<activity | |||
android:name=".MainActivity" | |||
android:exported="true" /> | |||
</application> | |||
</manifest> |
@@ -0,0 +1,29 @@ | |||
package cn.org.bjca.trust.android.imdemo.data; | |||
import cn.org.bjca.trust.android.imdemo.data.model.LoggedInUser; | |||
import java.io.IOException; | |||
/** | |||
* Class that handles authentication w/ login credentials and retrieves user information. | |||
*/ | |||
public class LoginDataSource { | |||
public Result<LoggedInUser> login(String username, String password) { | |||
try { | |||
// TODO: handle loggedInUser authentication | |||
LoggedInUser fakeUser = | |||
new LoggedInUser(username, | |||
java.util.UUID.randomUUID().toString(), | |||
"123456"); | |||
return new Result.Success<>(fakeUser); | |||
} catch (Exception e) { | |||
return new Result.Error(new IOException("Error logging in", e)); | |||
} | |||
} | |||
public void logout() { | |||
// TODO: revoke authentication | |||
} | |||
} |
@@ -0,0 +1,54 @@ | |||
package cn.org.bjca.trust.android.imdemo.data; | |||
import cn.org.bjca.trust.android.imdemo.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 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 | |||
} | |||
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; | |||
} | |||
} |
@@ -0,0 +1,48 @@ | |||
package cn.org.bjca.trust.android.imdemo.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; | |||
} | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
package cn.org.bjca.trust.android.imdemo.data.model; | |||
/** | |||
* Data class that captures user information for logged in users retrieved from LoginRepository | |||
*/ | |||
public class LoggedInUser { | |||
private final String userId; | |||
private final String displayName; | |||
private final String userSig; | |||
public LoggedInUser(String userId, String displayName, String userSig) { | |||
this.userId = userId; | |||
this.displayName = displayName; | |||
this.userSig = userSig; | |||
} | |||
public String getUserId() { | |||
return userId; | |||
} | |||
public String getDisplayName() { | |||
return displayName; | |||
} | |||
public String getUserSig() { | |||
return userSig; | |||
} | |||
} |
@@ -0,0 +1,19 @@ | |||
package cn.org.bjca.trust.android.imdemo.ui.login; | |||
import cn.org.bjca.trust.android.imdemo.data.model.LoggedInUser; | |||
/** | |||
* Class exposing authenticated user details to the UI. | |||
*/ | |||
class LoggedInUserView { | |||
private LoggedInUser user; | |||
//... other data fields that may be accessible to the UI | |||
LoggedInUserView(LoggedInUser user) { | |||
this.user = user; | |||
} | |||
LoggedInUser getDisplayName() { | |||
return user; | |||
} | |||
} |
@@ -0,0 +1,143 @@ | |||
package cn.org.bjca.trust.android.imdemo.ui.login; | |||
import android.app.Activity; | |||
import android.content.Intent; | |||
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.ViewModelProvider; | |||
import cn.org.bjca.trust.android.imdemo.MainActivity; | |||
import cn.org.bjca.trust.android.imdemo.databinding.ActivityLoginBinding; | |||
import cn.org.bjca.trust.android.lib.im.SzyxPush; | |||
import cn.org.bjca.trust.android.lib.im.kit.IMSDKCallback; | |||
public class LoginActivity extends AppCompatActivity { | |||
private LoginViewModel loginViewModel; | |||
private ActivityLoginBinding binding; | |||
@Override | |||
public void onCreate(Bundle savedInstanceState) { | |||
super.onCreate(savedInstanceState); | |||
binding = ActivityLoginBinding.inflate(getLayoutInflater()); | |||
setContentView(binding.getRoot()); | |||
loginViewModel = new ViewModelProvider(this, new LoginViewModelFactory()) | |||
.get(LoginViewModel.class); | |||
final EditText usernameEditText = binding.username; | |||
final EditText passwordEditText = binding.password; | |||
final Button loginButton = binding.login; | |||
final ProgressBar loadingProgressBar = binding.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()); | |||
} | |||
} | |||
}); | |||
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) { | |||
SzyxPush.getInstance().login(model.getDisplayName().getUserId(), model.getDisplayName().getUserSig(), new IMSDKCallback() { | |||
@Override | |||
public void success() { | |||
setResult(Activity.RESULT_OK); | |||
startActivity(new Intent(LoginActivity.this, MainActivity.class)); | |||
//Complete and destroy login activity once successful | |||
finish(); | |||
} | |||
@Override | |||
public void failed(int code, String error) { | |||
Toast.makeText(getApplicationContext(), error, Toast.LENGTH_SHORT).show(); | |||
} | |||
}); | |||
} | |||
private void showLoginFailed(@StringRes Integer errorString) { | |||
Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show(); | |||
} | |||
} |
@@ -0,0 +1,40 @@ | |||
package cn.org.bjca.trust.android.imdemo.ui.login; | |||
import androidx.annotation.Nullable; | |||
/** | |||
* Data validation state of the login form. | |||
*/ | |||
class LoginFormState { | |||
@Nullable | |||
private Integer usernameError; | |||
@Nullable | |||
private Integer passwordError; | |||
private 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; | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
package cn.org.bjca.trust.android.imdemo.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; | |||
} | |||
} |
@@ -0,0 +1,70 @@ | |||
package cn.org.bjca.trust.android.imdemo.ui.login; | |||
import androidx.lifecycle.LiveData; | |||
import androidx.lifecycle.MutableLiveData; | |||
import androidx.lifecycle.ViewModel; | |||
import android.util.Patterns; | |||
import cn.org.bjca.trust.android.imdemo.data.LoginRepository; | |||
import cn.org.bjca.trust.android.imdemo.data.Result; | |||
import cn.org.bjca.trust.android.imdemo.data.model.LoggedInUser; | |||
import cn.org.bjca.trust.android.imdemo.R; | |||
public class LoginViewModel extends ViewModel { | |||
private MutableLiveData<LoginFormState> loginFormState = new MutableLiveData<>(); | |||
private MutableLiveData<LoginResult> loginResult = new MutableLiveData<>(); | |||
private LoginRepository loginRepository; | |||
LoginViewModel(LoginRepository loginRepository) { | |||
this.loginRepository = loginRepository; | |||
} | |||
LiveData<LoginFormState> getLoginFormState() { | |||
return loginFormState; | |||
} | |||
LiveData<LoginResult> getLoginResult() { | |||
return loginResult; | |||
} | |||
public void login(String username, String password) { | |||
// can be launched in a separate asynchronous job | |||
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))); | |||
} else { | |||
loginResult.setValue(new LoginResult(R.string.login_failed)); | |||
} | |||
} | |||
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; | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
package cn.org.bjca.trust.android.imdemo.ui.login; | |||
import androidx.lifecycle.ViewModel; | |||
import androidx.lifecycle.ViewModelProvider; | |||
import androidx.annotation.NonNull; | |||
import cn.org.bjca.trust.android.imdemo.data.LoginDataSource; | |||
import cn.org.bjca.trust.android.imdemo.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"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,71 @@ | |||
<?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_marginTop="96dp" | |||
android:autofillHints="@string/prompt_email" | |||
android:hint="@string/prompt_email" | |||
android:inputType="textEmailAddress" | |||
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_marginTop="8dp" | |||
android:autofillHints="@string/prompt_password" | |||
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_marginTop="16dp" | |||
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_marginTop="64dp" | |||
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> |
@@ -0,0 +1,78 @@ | |||
<?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"> | |||
<androidx.constraintlayout.widget.ConstraintLayout | |||
android:layout_width="840dp" | |||
android:layout_height="match_parent" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintStart_toStartOf="parent"> | |||
<EditText | |||
android:id="@+id/username" | |||
android:layout_width="0dp" | |||
android:layout_height="wrap_content" | |||
android:layout_marginTop="96dp" | |||
android:autofillHints="@string/prompt_email" | |||
android:hint="@string/prompt_email" | |||
android:inputType="textEmailAddress" | |||
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_marginTop="8dp" | |||
android:autofillHints="@string/prompt_password" | |||
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_marginTop="16dp" | |||
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_marginTop="64dp" | |||
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> | |||
</androidx.constraintlayout.widget.ConstraintLayout> |
@@ -0,0 +1,73 @@ | |||
<?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_marginTop="96dp" | |||
android:autofillHints="@string/prompt_email" | |||
android:hint="@string/prompt_email" | |||
android:inputType="phone" | |||
android:text="13800000000" | |||
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_marginTop="8dp" | |||
android:autofillHints="@string/prompt_password" | |||
android:hint="@string/prompt_password" | |||
android:imeActionLabel="@string/action_sign_in_short" | |||
android:text="123456" | |||
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_marginTop="16dp" | |||
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_marginTop="64dp" | |||
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> |
@@ -0,0 +1,3 @@ | |||
<resources> | |||
<dimen name="activity_horizontal_margin">48dp</dimen> | |||
</resources> |
@@ -0,0 +1,3 @@ | |||
<resources> | |||
<dimen name="activity_horizontal_margin">200dp</dimen> | |||
</resources> |
@@ -0,0 +1,3 @@ | |||
<resources> | |||
<dimen name="activity_horizontal_margin">48dp</dimen> | |||
</resources> |
@@ -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> |
@@ -1,3 +1,12 @@ | |||
<resources> | |||
<string name="app_name">ImAndroid</string> | |||
<string name="title_activity_login">LoginActivity</string> | |||
<string name="prompt_email">Phone</string> | |||
<string name="prompt_password">Password</string> | |||
<string name="action_sign_in">Sign in or register</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> | |||
</resources> |
@@ -5,7 +5,7 @@ pluginManagement { | |||
allowInsecureProtocol true | |||
} | |||
maven { | |||
url 'http://nexus.51trust.net/repository/maven-public/' | |||
url 'http://nexus.51trust.net/repository/gradle-plugin/' | |||
allowInsecureProtocol true | |||
} | |||
} | |||
@@ -18,7 +18,7 @@ dependencyResolutionManagement { | |||
allowInsecureProtocol true | |||
} | |||
maven { | |||
url 'http://nexus.51trust.net/repository/maven-public/' | |||
url 'http://nexus.51trust.net/repository/gradle-plugin/' | |||
allowInsecureProtocol true | |||
} | |||
} | |||