@@ -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"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||
<project version="4"> | <project version="4"> | ||||
<component name="VcsDirectoryMappings"> | <component name="VcsDirectoryMappings"> | ||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" /> | |||||
<mapping directory="$PROJECT_DIR$" vcs="Git" /> | |||||
<mapping directory="" vcs="Git" /> | |||||
</component> | </component> | ||||
</project> | </project> |
@@ -7,7 +7,7 @@ android { | |||||
compileSdk 33 | compileSdk 33 | ||||
defaultConfig { | defaultConfig { | ||||
minSdk 24 | |||||
minSdk 26 | |||||
targetSdk 33 | targetSdk 33 | ||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||
@@ -1,19 +1,22 @@ | |||||
plugins { | plugins { | ||||
id 'com.android.library' | id 'com.android.library' | ||||
id "io.sentry.android.gradle" version "3.4.2" | |||||
} | } | ||||
def versionCode = 1 | |||||
def versionName = "0.0.1.011" | |||||
android { | android { | ||||
namespace 'cn.org.bjca.trust.android.lib.im' | namespace 'cn.org.bjca.trust.android.lib.im' | ||||
compileSdk 33 | compileSdk 33 | ||||
defaultConfig { | defaultConfig { | ||||
minSdk 24 | |||||
minSdk 26 | |||||
targetSdk 33 | targetSdk 33 | ||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||
consumerProguardFiles "consumer-rules.pro" | consumerProguardFiles "consumer-rules.pro" | ||||
versionCode 1 | |||||
versionName '1.0.0' | |||||
buildConfigField("String", "versionName", "\"${versionName}\"") | |||||
} | } | ||||
buildTypes { | buildTypes { | ||||
@@ -31,9 +34,25 @@ android { | |||||
dependencies { | dependencies { | ||||
api project(path: ':SzyxBaseSdk') | api project(path: ':SzyxBaseSdk') | ||||
implementation 'androidx.appcompat:appcompat:1.4.1' | implementation 'androidx.appcompat:appcompat:1.4.1' | ||||
implementation 'com.google.android.material:material:1.5.0' | implementation 'com.google.android.material:material:1.5.0' | ||||
testImplementation 'junit:junit:4.13.2' | testImplementation 'junit:junit:4.13.2' | ||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3' | androidTestImplementation 'androidx.test.ext:junit:1.1.3' | ||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' | 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"?> | <?xml version="1.0" encoding="utf-8"?> | ||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> | <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> | </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; | 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.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 { | public class SzyxPush { | ||||
private static final class SdkInterfaceHolder { | private static final class SdkInterfaceHolder { | ||||
static final SdkInterface instance = new PushSdkManager(); | |||||
static final SdkInterface instance = new SZYXImManager(); | |||||
} | } | ||||
public static SdkInterface getInstance() { | 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 { | defaultConfig { | ||||
applicationId "cn.org.bjca.trust.android.imdemo" | applicationId "cn.org.bjca.trust.android.imdemo" | ||||
minSdk 24 | |||||
minSdk 26 | |||||
targetSdk 33 | targetSdk 33 | ||||
versionCode 1 | versionCode 1 | ||||
versionName '1.0.0' | versionName '1.0.0' | ||||
@@ -26,6 +26,9 @@ android { | |||||
sourceCompatibility JavaVersion.VERSION_1_8 | sourceCompatibility JavaVersion.VERSION_1_8 | ||||
targetCompatibility JavaVersion.VERSION_1_8 | targetCompatibility JavaVersion.VERSION_1_8 | ||||
} | } | ||||
buildFeatures { | |||||
viewBinding true | |||||
} | |||||
} | } | ||||
dependencies { | dependencies { | ||||
@@ -34,6 +37,9 @@ dependencies { | |||||
implementation 'androidx.appcompat:appcompat:1.4.1' | implementation 'androidx.appcompat:appcompat:1.4.1' | ||||
implementation 'com.google.android.material:material:1.5.0' | implementation 'com.google.android.material:material:1.5.0' | ||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3' | 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' | testImplementation 'junit:junit:4.13.2' | ||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3' | androidTestImplementation 'androidx.test.ext:junit:1.1.3' | ||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' |
@@ -1,6 +1,7 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
xmlns:tools="http://schemas.android.com/tools"> | xmlns:tools="http://schemas.android.com/tools"> | ||||
<uses-permission android:name="android.permission.INTERNET" /> | |||||
<application | <application | ||||
android:name=".MyApplication" | android:name=".MyApplication" | ||||
@@ -13,14 +14,18 @@ | |||||
android:theme="@style/Theme.ImAndroid" | android:theme="@style/Theme.ImAndroid" | ||||
tools:targetApi="31"> | tools:targetApi="31"> | ||||
<activity | <activity | ||||
android:name=".MainActivity" | |||||
android:exported="true"> | |||||
android:name=".ui.login.LoginActivity" | |||||
android:exported="true" | |||||
android:label="@string/title_activity_login"> | |||||
<intent-filter> | <intent-filter> | ||||
<action android:name="android.intent.action.MAIN" /> | <action android:name="android.intent.action.MAIN" /> | ||||
<category android:name="android.intent.category.LAUNCHER" /> | <category android:name="android.intent.category.LAUNCHER" /> | ||||
</intent-filter> | </intent-filter> | ||||
</activity> | </activity> | ||||
<activity | |||||
android:name=".MainActivity" | |||||
android:exported="true" /> | |||||
</application> | </application> | ||||
</manifest> | </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> | <resources> | ||||
<string name="app_name">ImAndroid</string> | <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> | </resources> |
@@ -5,7 +5,7 @@ pluginManagement { | |||||
allowInsecureProtocol true | allowInsecureProtocol true | ||||
} | } | ||||
maven { | maven { | ||||
url 'http://nexus.51trust.net/repository/maven-public/' | |||||
url 'http://nexus.51trust.net/repository/gradle-plugin/' | |||||
allowInsecureProtocol true | allowInsecureProtocol true | ||||
} | } | ||||
} | } | ||||
@@ -18,7 +18,7 @@ dependencyResolutionManagement { | |||||
allowInsecureProtocol true | allowInsecureProtocol true | ||||
} | } | ||||
maven { | maven { | ||||
url 'http://nexus.51trust.net/repository/maven-public/' | |||||
url 'http://nexus.51trust.net/repository/gradle-plugin/' | |||||
allowInsecureProtocol true | allowInsecureProtocol true | ||||
} | } | ||||
} | } | ||||