Commit 4310c4a1 by wangtao

moduel解耦

parent 50f69bbc
......@@ -64,6 +64,31 @@
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
<XML>
<option name="XML_KEEP_LINE_BREAKS" value="false" />
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
......
package com.hikcreate.app;
import android.content.Context;
import android.content.res.Configuration;
import android.util.Log;
import com.hikcreate.base.BaseAppLogic;
/**
* 类说明
*
* @author wangtao55
* @date 2019/9/23
......@@ -14,22 +14,22 @@ import com.hikcreate.base.BaseAppLogic;
*/
public class AppAppLogic extends BaseAppLogic {
@Override
public void onCreate() {
public void onCreate(Context context) {
Log.v("AppAppLogic","onCreate--------------------->");
}
@Override
public void onTerminate() {
public void onTerminate(Context context) {
Log.v("AppAppLogic","onTerminate--------------------->");
}
@Override
public void onLowMemory() {
public void onLowMemory(Context context) {
Log.v("AppAppLogic","onLowMemory--------------------->");
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
public void onConfigurationChanged(Context context,Configuration newConfig) {
Log.v("AppAppLogic","onConfigurationChanged--------------------->");
}
}
......@@ -21,6 +21,7 @@ public class CommonApplication extends Application {
registerActivityLife();
AppContext.getInstance().logicOnCreate(this);
}
private void registerAppInit(){
//登录组件
AppContext.getInstance().registerAppLogic(AppInitConfig.LOGIN_CONFIG);
......@@ -36,15 +37,17 @@ public class CommonApplication extends Application {
}
@Override
public void onTerminate() {
super.onTerminate();
AppContext.getInstance().logicOnTerminate();
AppContext.getInstance().logicOnTerminate(this);
}
@Override
public void onLowMemory() {
super.onLowMemory();
AppContext.getInstance().logicOnLowMemory();
AppContext.getInstance().logicOnLowMemory(this);
}
}
......@@ -2,9 +2,14 @@ package com.hikcreate.ui;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
import com.hikcreate.data.config.AppProvider;
import com.hikcreate.module_router.router.RouterResponse;
import com.hikcreate.module_router.tools.ModuleRouterUtil;
import com.message.bean.GeneralMessageBean;
import com.message.driver.MessageWrap;
import com.google.gson.Gson;
......@@ -13,6 +18,8 @@ import com.hikcreate.passport.TestFragment;
import com.hikcreate.temp.TestBean;
import com.module.hikcreate.R;
import java.util.HashMap;
/**
* 类说明
......@@ -28,10 +35,41 @@ public class testActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.app_test);
findViewById(R.id.mBtnLogin).setOnClickListener(v -> {
Intent mIntent = new Intent(testActivity.this, testLoginActivity.class);
startActivity(mIntent);
RouterResponse response = ModuleRouterUtil.getRouterResponse(getApplicationContext(),
AppProvider.LOGIN_PROVIDER,AppProvider.GET_USER_INFO_ACTION);
if(response != null){
if(response.getObject() != null){
HashMap value = (HashMap) response.getObject();
Toast.makeText(getApplicationContext(), (String) value.get("mobile"),Toast.LENGTH_LONG).show();
Toast.makeText(getApplicationContext(),(String)value.get("name"),Toast.LENGTH_LONG).show();
}
Toast.makeText(getApplicationContext(),response.getData(),Toast.LENGTH_LONG).show();
}
});
findViewById(R.id.mBtnPassport).setOnClickListener(v -> {
new Thread(() -> {
try {
RouterResponse response = ModuleRouterUtil.getRouterResponse(getApplicationContext(),
AppProvider.LOGIN_PROVIDER,AppProvider.GET_USER_INFO_ACTION);
final String result = response.get();
new Handler().post(() -> {
try {
Toast.makeText(testActivity.this, result, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}).start();
});
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
......
package com.hikcreate.base;
import android.content.Context;
import android.content.res.Configuration;
import android.util.Log;
/**
*
* @author wangtao55
* @date 2019/9/23
* @mail wangtao55@hikcreate.com
*/
public class AppAppLogic extends BaseAppLogic {
@Override
public void onCreate(Context context) {
Log.v("AppAppLogic","onCreate--------------------->");
}
@Override
public void onTerminate(Context context) {
Log.v("AppAppLogic","onTerminate--------------------->");
}
@Override
public void onLowMemory(Context context) {
Log.v("AppAppLogic","onLowMemory--------------------->");
}
@Override
public void onConfigurationChanged(Context context,Configuration newConfig) {
Log.v("AppAppLogic","onConfigurationChanged--------------------->");
}
}
package com.hikcreate.base;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.multidex.MultiDex;
import java.util.ArrayList;
import java.util.List;
......@@ -51,7 +53,7 @@ public class AppContext {
Class appClass = Class.forName(logicClass);
BaseAppLogic appLogic = (BaseAppLogic) appClass.newInstance();
logicClassList.add(appLogic);
appLogic.onCreate();
appLogic.onCreate(application);
}catch (Exception e){
e.printStackTrace();
}
......@@ -68,30 +70,30 @@ public class AppContext {
init(application);
}
public void logicOnTerminate(){
public void logicOnTerminate(Application application){
for(BaseAppLogic logicClass : logicClassList){
try {
logicClass.onTerminate();
logicClass.onTerminate(application);
}catch (Exception e){
e.printStackTrace();
}
}
}
public void logicOnLowMemory(){
public void logicOnLowMemory(Application application){
for(BaseAppLogic logicClass : logicClassList){
try {
logicClass.onLowMemory();
logicClass.onLowMemory(application);
}catch (Exception e){
e.printStackTrace();
}
}
}
public void logicOnConfigurationChanged(Configuration newConfig){
public void logicOnConfigurationChanged(Application application,Configuration newConfig){
for(BaseAppLogic logicClass : logicClassList){
try {
logicClass.onConfigurationChanged(newConfig);
logicClass.onConfigurationChanged(application,newConfig);
}catch (Exception e){
e.printStackTrace();
}
......
......@@ -13,5 +13,5 @@ public class AppInitConfig {
public static final String LOGIN_CONFIG = "com.init.LoginAppLogic";
//App
public static final String APP_CONFIG = "com.hikcreate.app.AppAppLogic";
public static final String APP_CONFIG = "com.hikcreate.base.AppAppLogic";
}
......@@ -14,7 +14,7 @@ public class AppProvider {
public static final String PWD_PROVIDER = "pwd_provider";
//登录模块Aciton
public static final String LOGIN_ACTION = "login_action";
public static final String GET_USER_INFO_ACTION = "user_action";
public static final String PWD_ACTION = "pwd_action";
}
......@@ -8,26 +8,29 @@ import com.hikcreate.module_router.ModuleActionResult;
import java.util.HashMap;
/**
* 登录action
* 用户信息action
*
* @author wangtao55
* @date 2019/9/24
* @mail wangtao55@hikcreate.com
*/
public class LoginAction extends ModuleAction {
public class GetUserInfoAction extends ModuleAction {
@Override
public boolean isAsync(Context context, HashMap<String, String> requestData) {
public boolean isAsync(HashMap<String, String> requestData) {
return false;
}
@Override
public ModuleActionResult invoke(Context context, HashMap<String, String> requestData) {
ModuleActionResult result = new ModuleActionResult.Builder()
HashMap<String,String> hashMap = new HashMap<>();
hashMap.put("name","test");
hashMap.put("mobile","15828028350");
return new ModuleActionResult.Builder()
.code(ModuleActionResult.CODE_SUCCESS)
.msg("登录成功")
.data("我是来自登录界面的诗句")
.object(null)
.data("我是来自登录界面的数据")
.object(hashMap)
.build();
return null;
}
}
package com.hikcreate.login.moduel.router.provider;
import com.hikcreate.login.moduel.router.action.change.ChangePwd;
import com.hikcreate.login.moduel.router.action.login.LoginAction;
import com.hikcreate.module_router.ModuleProvider;
import static com.hikcreate.data.config.AppProvider.PWD_ACTION;
......
package com.hikcreate.login.moduel.router.provider;
import com.hikcreate.login.moduel.router.action.login.LoginAction;
import com.hikcreate.login.moduel.router.action.login.GetUserInfoAction;
import com.hikcreate.module_router.ModuleProvider;
import static com.hikcreate.data.config.AppProvider.GET_USER_INFO_ACTION;
import static com.hikcreate.data.config.AppProvider.LOGIN_ACTION;
/**
* 登录Provider
......@@ -15,6 +15,7 @@ import static com.hikcreate.data.config.AppProvider.LOGIN_ACTION;
public class LoginProvider extends ModuleProvider {
@Override
protected void registerActions() {
registerAction(LOGIN_ACTION,new LoginAction());
registerAction(GET_USER_INFO_ACTION,new GetUserInfoAction());
}
}
......@@ -10,6 +10,7 @@ import com.hikcreate.data.config.AppProvider;
import com.hikcreate.module_router.LocalRouter;
import com.hikcreate.module_router.router.RouterRequest;
import com.hikcreate.module_router.router.RouterResponse;
import com.hikcreate.module_router.tools.ModuleRouterUtil;
/**
* 类说明
......@@ -27,13 +28,8 @@ public class testLoginActivity extends AppCompatActivity {
findViewById(R.id.mBtnLogin).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//
// RouterResponse response = LocalRouter.getInstance(AppContext.getInstance())
// .route(testLoginActivity.this,
// RouterRequest.obtain(testLoginActivity.this).provider(AppProvider.LOGIN_ACTION)
// .action("sync")
// .data("1", "Hello")
// .data("2", "World"));
}
});
......
......@@ -5,10 +5,6 @@ import android.app.Application;
import com.hikcreate.base.AppContext;
import com.hikcreate.data.config.ActivityLifeCycleInitConfig;
import com.hikcreate.data.config.AppInitConfig;
import com.hikcreate.data.config.AppProvider;
import com.hikcreate.login.moduel.router.provider.ChangePwdProvider;
import com.hikcreate.login.moduel.router.provider.LoginProvider;
import com.hikcreate.module_router.LocalRouter;
/**
*
......@@ -26,22 +22,17 @@ public class CommonApplication extends Application {
AppContext.getInstance().logicOnCreate(this);
}
private void initProviderCreate(){
LocalRouter.getInstance(AppContext.getInstance()).registerProvider(AppProvider.LOGIN_PROVIDER,new LoginProvider());
LocalRouter.getInstance(AppContext.getInstance()).registerProvider(AppProvider.PWD_PROVIDER,new ChangePwdProvider());
}
@Override
public void onTerminate() {
super.onTerminate();
AppContext.getInstance().logicOnTerminate();
AppContext.getInstance().logicOnTerminate(this);
}
@Override
public void onLowMemory() {
super.onLowMemory();
AppContext.getInstance().logicOnLowMemory();
AppContext.getInstance().logicOnLowMemory(this);
}
}
package com.init;
import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
import android.util.Log;
import com.hikcreate.base.AppContext;
import com.hikcreate.base.BaseAppLogic;
import com.hikcreate.data.config.AppProvider;
import com.hikcreate.login.moduel.router.provider.ChangePwdProvider;
import com.hikcreate.login.moduel.router.provider.LoginProvider;
import com.hikcreate.module_router.LocalRouter;
/**
* 类说明
......@@ -14,22 +21,31 @@ import com.hikcreate.base.BaseAppLogic;
*/
public class LoginAppLogic extends BaseAppLogic {
@Override
public void onCreate() {
public void onCreate(Context application) {
Log.v("LoginAppLogic","onCreate--------------------->");
initProviderCreate();
}
@Override
public void onTerminate() {
public void onTerminate(Context applicatio) {
Log.v("LoginAppLogic","onTerminate--------------------->");
}
@Override
public void onLowMemory() {
public void onLowMemory(Context applicatio) {
Log.v("LoginAppLogic","onLowMemory--------------------->");
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
public void onConfigurationChanged(Context applicatio,Configuration newConfig) {
Log.v("LoginAppLogic","onConfigurationChanged--------------------->");
}
private void initProviderCreate(){
LocalRouter.getInstance(AppContext.
getInstance().getApplication()).
registerProvider(AppProvider.LOGIN_PROVIDER,
new LoginProvider());
}
}
package com.hikcreate.base;
import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
/**
......@@ -13,10 +14,10 @@ import android.content.res.Configuration;
public abstract class BaseAppLogic {
private Application mApplication;
abstract public void onCreate();
abstract public void onTerminate();
abstract public void onLowMemory();
abstract public void onConfigurationChanged(Configuration newConfig);
abstract public void onCreate(Context context);
abstract public void onTerminate(Context context);
abstract public void onLowMemory(Context context);
abstract public void onConfigurationChanged(Context context,Configuration newConfig);
}
package com.hikcreate.module_router;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import com.hikcreate.base.AppContext;
import com.hikcreate.module_router.router.RouterRequest;
import com.hikcreate.module_router.router.RouterResponse;
import com.hikcreate.module_router.tools.Logger;
import com.hikcreate.module_router.tools.ProcessUtil;
import java.util.HashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static android.content.Context.BIND_AUTO_CREATE;
/**
* @author wangtao55
......@@ -32,17 +24,17 @@ public class LocalRouter {
private String mProcessName = ProcessUtil.UNKNOWN_PROCESS_NAME;
private static LocalRouter sInstance = null;
private HashMap<String, ModuleProvider> mProviders = null;
private AppContext mApplication;
private static ExecutorService threadPool = null;
private Context mContext;
private LocalRouter(AppContext context) {
mApplication = context;
mProcessName = ProcessUtil.getProcessName(context.getApplication(), ProcessUtil.getMyProcessId());
private LocalRouter(Context context) {
mContext = context;
mProcessName = ProcessUtil.getProcessName(context, ProcessUtil.getMyProcessId());
mProviders = new HashMap<>();
}
public static synchronized LocalRouter getInstance(@NonNull AppContext context) {
public static synchronized LocalRouter getInstance(@NonNull Context context) {
if (sInstance == null) {
sInstance = new LocalRouter(context);
}
......@@ -63,15 +55,17 @@ public class LocalRouter {
boolean answerWiderAsync(@NonNull RouterRequest routerRequest) {
if (mProcessName.equals(routerRequest.getDomain())) {
return findRequestAction(routerRequest).isAsync(mApplication.getApplication(), routerRequest.getData());
return findRequestAction(routerRequest).isAsync(routerRequest.getData());
} else {
return true;
}
}
public RouterResponse route(Context context, @NonNull RouterRequest routerRequest) throws Exception {
public RouterResponse route(@NonNull RouterRequest routerRequest){
Logger.d(TAG, "Process:" + mProcessName + "\nLocal route start: " + System.currentTimeMillis());
RouterResponse routerResponse = new RouterResponse();
// Local request
if (mProcessName.equals(routerRequest.getDomain())) {
Object attachment = routerRequest.getAndClearObject();
......@@ -80,17 +74,19 @@ public class LocalRouter {
ModuleAction targetAction = findRequestAction(routerRequest);
routerRequest.isIdle.set(true);
Logger.d(TAG, "Process:" + mProcessName + "\nLocal find action end: " + System.currentTimeMillis());
routerResponse.mIsAsync = attachment == null ? targetAction.isAsync(context, params) : targetAction.isAsync(context, params, attachment);
routerResponse.mIsAsync = attachment == null ? targetAction.isAsync(params) :
targetAction.isAsync(params, attachment);
// Sync result, return the result immediately.
if (!routerResponse.mIsAsync) {
ModuleActionResult result = attachment == null ? targetAction.invoke(context, params) : targetAction.invoke(context, params, attachment);
ModuleActionResult result = attachment == null ? targetAction.invoke(mContext, params) :
targetAction.invoke(mContext, params, attachment);
routerResponse.mResultString = result.toString();
routerResponse.mObject = result.getObject();
Logger.d(TAG, "Process:" + mProcessName + "\nLocal sync end: " + System.currentTimeMillis());
}
// Async result, use the thread pool to execute the task.
else {
LocalTask task = new LocalTask(routerResponse, params, attachment, context, targetAction);
LocalTask task = new LocalTask(routerResponse, params, attachment, mContext, targetAction);
routerResponse.mAsyncResponse = getThreadPool().submit(task);
}
}
......@@ -99,8 +95,10 @@ public class LocalRouter {
private ModuleAction findRequestAction(RouterRequest routerRequest) {
ModuleProvider targetProvider = mProviders.get(routerRequest.getProvider());
ErrorAction defaultNotFoundAction = new ErrorAction(false, ModuleActionResult.CODE_NOT_FOUND, "Not found the action.");
ErrorAction defaultNotFoundAction = new ErrorAction(false,
ModuleActionResult.CODE_NOT_FOUND, "Not found the action.");
if (null == targetProvider) {
return defaultNotFoundAction;
} else {
......@@ -120,7 +118,8 @@ public class LocalRouter {
private ModuleAction mAction;
private Object mObject;
LocalTask(RouterResponse routerResponse, HashMap<String, String> requestData, Object object, Context context, ModuleAction maAction) {
LocalTask(RouterResponse routerResponse, HashMap<String, String>
requestData, Object object, Context context, ModuleAction maAction) {
this.mContext = context;
this.mResponse = routerResponse;
this.mRequestData = requestData;
......@@ -129,11 +128,17 @@ public class LocalRouter {
}
@Override
public String call() throws Exception {
ModuleActionResult result = mObject == null ? mAction.invoke(mContext, mRequestData) : mAction.invoke(mContext, mRequestData, mObject);
public String call() {
try {
ModuleActionResult result = mObject == null ? mAction.invoke(mContext, mRequestData)
: mAction.invoke(mContext, mRequestData, mObject);
mResponse.mObject = result.getObject();
Logger.d(TAG, "Process:" + mProcessName + "\nLocal async end: " + System.currentTimeMillis());
return result.toString();
}catch (Exception e){
e.printStackTrace();
}
return "error";
}
}
......
......@@ -12,9 +12,9 @@ import java.util.HashMap;
*/
public abstract class ModuleAction {
public abstract boolean isAsync(Context context, HashMap<String,String> requestData);
public abstract boolean isAsync(HashMap<String,String> requestData);
public abstract ModuleActionResult invoke(Context context, HashMap<String,String> requestData);
public boolean isAsync(Context context, HashMap<String,String> requestData,Object object){
public boolean isAsync(HashMap<String,String> requestData,Object object){
return false;
}
public ModuleActionResult invoke(Context context, HashMap<String,String> requestData, Object object){
......
......@@ -4,6 +4,8 @@ package com.hikcreate.module_router;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
/**
*
* @author wangtao55
......@@ -33,6 +35,7 @@ public class ModuleActionResult {
this.msg = builder.mMsg;
this.data = builder.mData;
this.object = builder.mObject;
}
public Object getObject() {
......@@ -102,6 +105,7 @@ public class ModuleActionResult {
return this;
}
public Builder object(Object object) {
this.mObject = object;
return this;
......
......@@ -220,6 +220,16 @@ public class RouterRequest {
return this;
}
public RouterRequest data(HashMap<String,String> value) {
if(value == null){
return this;
}
for (Map.Entry<String, String> entry : value.entrySet()) {
this.data.put(entry.getKey(), entry.getValue());
}
return this;
}
public RouterRequest data(String key, String data) {
this.data.put(key, data);
......
......@@ -3,6 +3,7 @@ package com.hikcreate.module_router.router;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
......@@ -22,11 +23,12 @@ public class RouterResponse {
public Future<String> mAsyncResponse;
public Object mObject;
private static final int TIME_OUT = 30 * 1000;
private long mTimeOut = 0;
private long mTimeOut;
private boolean mHasGet = false;
int mCode = -1;
String mMessage = "";
String mData;
HashMap<String,String> keyValues;
......@@ -45,13 +47,18 @@ public class RouterResponse {
return mIsAsync;
}
public String get() throws Exception {
public String get(){
try{
if (mIsAsync) {
mResultString = mAsyncResponse.get(mTimeOut, TimeUnit.MILLISECONDS);
parseResult();
}else{
parseResult();
}
}catch (Exception e){
e.printStackTrace();
}
return mResultString;
}
......@@ -69,32 +76,34 @@ public class RouterResponse {
}
}
public int getCode() throws Exception {
public int getCode(){
if (!mHasGet) {
get();
}
return mCode;
}
public String getMessage() throws Exception {
public String getMessage(){
if (!mHasGet) {
get();
}
return mMessage;
}
public String getData() throws Exception {
public String getData() {
if (!mHasGet) {
get();
}
return mData;
}
public Object getObject() throws Exception {
public Object getObject(){
if (!mHasGet) {
get();
}
return mObject;
}
}
package com.hikcreate.module_router.tools;
import android.content.Context;
import com.hikcreate.module_router.LocalRouter;
import com.hikcreate.module_router.router.RouterRequest;
import com.hikcreate.module_router.router.RouterResponse;
import java.util.HashMap;
/**
* author : taowang
* date :2019/9/25
* description:
**/
public class ModuleRouterUtil {
public static RouterResponse getRouterResponse(Context context,String provider,String action){
return LocalRouter.getInstance(context).route(RouterRequest.obtain(context).provider(provider).action(action));
}
public static RouterResponse getRouterResponse(Context context, String provider, String action,
HashMap<String,String> value){
return LocalRouter.getInstance(context).route(RouterRequest.obtain(context).provider(provider).data(value).action(action));
}
public static RouterResponse startRouter(Context context,RouterRequest request){
return LocalRouter.getInstance(context).route(request);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment