diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 56efcef..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -ImAndroid \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 288b36b..35eb1dd 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,7 +1,6 @@ - - + \ No newline at end of file diff --git a/SzyxBaseSdk/build.gradle b/SzyxBaseSdk/build.gradle index 94f35cb..15df89e 100644 --- a/SzyxBaseSdk/build.gradle +++ b/SzyxBaseSdk/build.gradle @@ -7,7 +7,7 @@ android { compileSdk 33 defaultConfig { - minSdk 24 + minSdk 26 targetSdk 33 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/SzyxImSdk/build.gradle b/SzyxImSdk/build.gradle index f2b2905..f944707 100644 --- a/SzyxImSdk/build.gradle +++ b/SzyxImSdk/build.gradle @@ -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' + + } \ No newline at end of file diff --git a/SzyxImSdk/src/main/AndroidManifest.xml b/SzyxImSdk/src/main/AndroidManifest.xml index a5918e6..7834f97 100644 --- a/SzyxImSdk/src/main/AndroidManifest.xml +++ b/SzyxImSdk/src/main/AndroidManifest.xml @@ -1,4 +1,32 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/SZYXDbHelper.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/SZYXDbHelper.java new file mode 100644 index 0000000..814791a --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/SZYXDbHelper.java @@ -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(); + } + } +} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/SzyxPush.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/SzyxPush.java index 079bb5e..781667d 100644 --- a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/SzyxPush.java +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/SzyxPush.java @@ -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() { diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/cfg/Constant.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/cfg/Constant.java new file mode 100644 index 0000000..5e7fb73 --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/cfg/Constant.java @@ -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; + } +} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/common/CommonHelper.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/common/CommonHelper.java new file mode 100644 index 0000000..27310f0 --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/common/CommonHelper.java @@ -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(); + } +} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/common/DeviceHelper.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/common/DeviceHelper.java new file mode 100644 index 0000000..b2c722c --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/common/DeviceHelper.java @@ -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 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); + } +} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/db/BaseEntity.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/db/BaseEntity.java new file mode 100644 index 0000000..44bd291 --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/db/BaseEntity.java @@ -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; + } +} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/db/ImDatabase.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/db/ImDatabase.java new file mode 100644 index 0000000..a3e0315 --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/db/ImDatabase.java @@ -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(); + +} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/db/device/DeviceDao.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/db/device/DeviceDao.java new file mode 100644 index 0000000..773e971 --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/db/device/DeviceDao.java @@ -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 getAll(); + + @Insert + void insertAll(DeviceEntity... devices); + + @Update + void update(DeviceEntity device); + + @Delete + void delete(DeviceEntity device); +} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/db/device/DeviceEntity.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/db/device/DeviceEntity.java new file mode 100644 index 0000000..f746077 --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/db/device/DeviceEntity.java @@ -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 + '\'' + + '}'; + } +} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/http/HeaderInterceptor.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/http/HeaderInterceptor.java new file mode 100644 index 0000000..ffb8105 --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/http/HeaderInterceptor.java @@ -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()); + } +} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/http/HttpManage.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/http/HttpManage.java new file mode 100644 index 0000000..eac9f91 --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/http/HttpManage.java @@ -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 getApi(final Class 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); + } +} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/http/HttpResult.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/http/HttpResult.java new file mode 100644 index 0000000..3ed9558 --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/http/HttpResult.java @@ -0,0 +1,37 @@ +package cn.org.bjca.trust.android.lib.im.http; + +public class HttpResult { + /** + * "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; + } +} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/manager/PushSdkManager.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/manager/PushSdkManager.java deleted file mode 100644 index 6452f36..0000000 --- a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/manager/PushSdkManager.java +++ /dev/null @@ -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; - } -} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/manager/SZYXImManager.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/manager/SZYXImManager.java new file mode 100644 index 0000000..107b771 --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/manager/SZYXImManager.java @@ -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; + } +} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/repository/Service.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/repository/Service.java new file mode 100644 index 0000000..b769965 --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/repository/Service.java @@ -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> test(@Path("id") String id); + + @POST("user/v1/login") + Observable> login(@Body LoginData loginData); +} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/repository/bean/LoginBean.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/repository/bean/LoginBean.java new file mode 100644 index 0000000..3861bd5 --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/repository/bean/LoginBean.java @@ -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; +} diff --git a/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/repository/data/LoginData.java b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/repository/data/LoginData.java new file mode 100644 index 0000000..d6e6180 --- /dev/null +++ b/SzyxImSdk/src/main/java/cn/org/bjca/trust/android/lib/im/repository/data/LoginData.java @@ -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; + } +} diff --git a/app/build.gradle b/app/build.gradle index 857e2f3..25cef0b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5b78bcc..b5ea9ac 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + + android:name=".ui.login.LoginActivity" + android:exported="true" + android:label="@string/title_activity_login"> + \ No newline at end of file diff --git a/app/src/main/java/cn/org/bjca/trust/android/imdemo/data/LoginDataSource.java b/app/src/main/java/cn/org/bjca/trust/android/imdemo/data/LoginDataSource.java new file mode 100644 index 0000000..d07b33e --- /dev/null +++ b/app/src/main/java/cn/org/bjca/trust/android/imdemo/data/LoginDataSource.java @@ -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 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/cn/org/bjca/trust/android/imdemo/data/LoginRepository.java b/app/src/main/java/cn/org/bjca/trust/android/imdemo/data/LoginRepository.java new file mode 100644 index 0000000..ab798a7 --- /dev/null +++ b/app/src/main/java/cn/org/bjca/trust/android/imdemo/data/LoginRepository.java @@ -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 login(String username, String password) { + // handle login + Result result = dataSource.login(username, password); + if (result instanceof Result.Success) { + setLoggedInUser(((Result.Success) result).getData()); + } + return result; + } +} \ No newline at end of file diff --git a/app/src/main/java/cn/org/bjca/trust/android/imdemo/data/Result.java b/app/src/main/java/cn/org/bjca/trust/android/imdemo/data/Result.java new file mode 100644 index 0000000..0d6a3ed --- /dev/null +++ b/app/src/main/java/cn/org/bjca/trust/android/imdemo/data/Result.java @@ -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 { + // 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 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; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/cn/org/bjca/trust/android/imdemo/data/model/LoggedInUser.java b/app/src/main/java/cn/org/bjca/trust/android/imdemo/data/model/LoggedInUser.java new file mode 100644 index 0000000..2054d06 --- /dev/null +++ b/app/src/main/java/cn/org/bjca/trust/android/imdemo/data/model/LoggedInUser.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoggedInUserView.java b/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoggedInUserView.java new file mode 100644 index 0000000..d17d903 --- /dev/null +++ b/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoggedInUserView.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginActivity.java b/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginActivity.java new file mode 100644 index 0000000..680fa4b --- /dev/null +++ b/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginActivity.java @@ -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() { + @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() { + @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(); + } +} \ No newline at end of file diff --git a/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginFormState.java b/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginFormState.java new file mode 100644 index 0000000..9659f1d --- /dev/null +++ b/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginFormState.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginResult.java b/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginResult.java new file mode 100644 index 0000000..1e313f9 --- /dev/null +++ b/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginResult.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginViewModel.java b/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginViewModel.java new file mode 100644 index 0000000..5f6bc76 --- /dev/null +++ b/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginViewModel.java @@ -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 = new MutableLiveData<>(); + private MutableLiveData loginResult = new MutableLiveData<>(); + private LoginRepository loginRepository; + + LoginViewModel(LoginRepository loginRepository) { + this.loginRepository = loginRepository; + } + + LiveData getLoginFormState() { + return loginFormState; + } + + LiveData getLoginResult() { + return loginResult; + } + + public void login(String username, String password) { + // can be launched in a separate asynchronous job + Result result = loginRepository.login(username, password); + + if (result instanceof Result.Success) { + LoggedInUser data = ((Result.Success) 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; + } +} \ No newline at end of file diff --git a/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginViewModelFactory.java b/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginViewModelFactory.java new file mode 100644 index 0000000..b50b699 --- /dev/null +++ b/app/src/main/java/cn/org/bjca/trust/android/imdemo/ui/login/LoginViewModelFactory.java @@ -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 create(@NonNull Class modelClass) { + if (modelClass.isAssignableFrom(LoginViewModel.class)) { + return (T) new LoginViewModel(LoginRepository.getInstance(new LoginDataSource())); + } else { + throw new IllegalArgumentException("Unknown ViewModel class"); + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout-w1240dp/activity_login.xml b/app/src/main/res/layout-w1240dp/activity_login.xml new file mode 100644 index 0000000..738fb65 --- /dev/null +++ b/app/src/main/res/layout-w1240dp/activity_login.xml @@ -0,0 +1,71 @@ + + + + + + + +