Personalize componentes de UI do contêiner MiniApp, como diálogos de autorização, páginas de erro e telas iniciais para Android, iOS e Flutter.
1. Contexto e objetivo
O contêiner MiniApp fornece componentes de UI padrão, como diálogos de autorização de dispositivo do usuário. Alguns clientes precisam personalizar parte ou todos esses componentes.



As soluções a seguir permitem a personalização total ou parcial da UI.
2. Arquitetura geral e sequência


3. Guia de desenvolvimento
3,1 Cenário nativo
3.1.1 Importar pacotes
// in build.gradle
implementation "com.aliyun.emas.suite.foundation:mini-app-adapter:1.8.14"
implementation "com.aliyun.emas.suite.foundation:windvane-mini-app:1.8.14"
implementation "com.aliyun.emas.suite.foundation:mini-app-plugin-base:1.8.14"
// in podfile
pod 'EMASMiniAppAdapter', '1.1.4'
pod 'EMASWindVaneMiniApp', '1.2.5'
//if use bluetooth feature please pod WindVaneBluetooth
pod 'WindVaneBluetooth', '1.0.2'
3.1.2 Interfaces de UI do SDK
No Android
public interface UIExtension {
// Used to create 'More' dialog, refer to 'figure 1' in the remarks for example style
public default Fragment createMoreDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Used to create 'Favorite' dialog, refer to 'figure 2' in the remarks for example style
public default Fragment createFavoriteDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Used to create 'Auth' dialog, refer to 'figure 3' in the remarks for example style
public default Fragment createUserDeviceInfoAuthDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Used to create 'Error' sub page, refer to 'figure 5' in the remarks for example style
public default Fragment createErrorPageFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Used to create 'Slash' sub page, refer to 'figure 6' in the remarks for example style
public default Fragment createSplashPageFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Used to create 'About US' dialog, refer to 'figure 8' in the remarks for example style
public default Fragment createAboutUsDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Used to create 'User Info Auth' sub page, refer to 'figure 7' in the remarks for example style
public default Fragment createUserInfoAuthPageFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
}
public class UIComponentParam {
private UIResultCallBack uiResultCallBack;
private Context context;
private HashMap<String, Object> params;
/**
* uiResultCallBack:The data returned
* data:Data sent out
* **/
public UIComponentParam(UIResultCallBack uiResultCallBack, HashMap<String, Object> data, Context context) {
this.uiResultCallBack = uiResultCallBack;
this.data = data;
this.context = context;
}
public UIResultCallBack getUiResultCallBack() {
return uiResultCallBack;
}
public HashMap<String, Object> getParams() {
return params;
}
public Context getContext() {
return context;
}
}
public interface UIResultCallBack{
void onComplete(String data);
}
public class UIResultDataUtil {
private static JSONObject buildResultData(Object data, String msg, String code) {
JSONObject returnData = new JSONObject();
try {
returnData.put("data", data != null ? data : EMPTY_DATA);
returnData.put("msg", msg != null ? msg : EMPTY_DATA);
returnData.put("code", code);
} catch (JSONException e) {
throw new IllegalArgumentException("JSON build failed", e);
}
return returnData;
}
public static String getResultData(JSONObject jsonObject, String msg, String code) {
return buildResultData(jsonObject, msg, code).toString();
}
public static String getResultData(String data, String msg, String code) {
return buildResultData(data, msg, code).toString();
}
public static String getResultData(String msg, String code) {
return buildResultData(null, msg, code).toString();
}
public static String getResultData(String code) {
return buildResultData(null, null, code).toString();
}
}
public class UIResultCodeConstants {
public static final String CANCEL = "CANCEL";
public static final String CONFIRM = "CONFIRM";
public static final String ERROR = "ERROR";
public static final String RESTART_MINIAPP = "RESTART_MINIAPP";
public static final String EXIT_MINIAPP = "EXIT_MINIAPP";
public static final String OPEN_ABOUT="OPEN_ABOUT";
public static final String OPEN_SETTING="OPEN_SETTING";
}
Como usar constantes no formato de dados de autorização:
CANCEL: Quando o usuário clicar no botão Cancelar na página, defina o
codecom esta constante. O SDK fechará a janela pop-up.CONFIRM: Ao clicar no botão Confirmar na página, defina o
codecom esta constante e retorne as informações relevantes emdata. O SDK fechará a janela pop-up.ERROR: Se o processamento do componente falhar, defina o
codecom esta constante e forneça uma mensagem de erro emmsg. O SDK fechará a janela pop-up e passará a mensagem de erro ao JavaScript para retorno ao miniapp.RESTART_MINIAPP: Caso o usuário clique no botão Atualizar/Reiniciar na página do miniapp, defina o
codecom esta constante. O SDK reabrirá e recarregará o miniapp.EXIT_MINIAPP: Quando o usuário clicar no botão Sair na página do miniapp, defina o
codecom esta constante. O SDK fechará o miniapp atual.
Como usar o onComplete e as constantes UIResultCodeConstants:
a. Em ErrorPageFragment:
Quando o usuário clicar no botão Sair na página do miniapp, defina o code como EXIT_MINIAPP.
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.EXIT_MINIAPP));
Se o usuário clicar no botão Atualizar/Reiniciar na página do miniapp, defina o code como RESTART_MINIAPP.
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.RESTART_MINIAPP));
b. Em UserDeviceInfoAuthDialogFragment:
O uiComponentParam.params tem a seguinte estrutura: authorize: Este parâmetro é transmitido quando o miniapp chama wv.authorize. Quando o parâmetro do método wv.authorize contém tanto "scope" quanto "scopes", "scopes" tem prioridade. Caso contrário, utiliza-se o parâmetro "scope". Além disso, o SDK consultará o status de autorização concedido anteriormente pelo miniapp e definirá o status de autorização para os escopos solicitados.
appId: ID do miniapp
userId: userId
miniAppInfo: Metainformações do miniapp.
/*This corresponds to the structure obtained by key 'authorize' in param in UIComponentParam
{
"authorize": {"location": true/false, "camera": true/false},
"appId": appId,
"userId": userInfo.userId,
"miniAppInfo": miniAppInfo
}
This corresponds to the structure obtained according to the key 'miniAppInfo' in param in UIComponentParam.
miniAppInfo:
{
"miniAppId": appId,
"miniAppName": appName,
"miniAppIcon": iconUrl,
"miniAppVersion": version,
"miniAppSlogan": slogan,
"miniAppDescription": description
}
*/
HashMap<String, Object> params = uiComponentParam.getParam();
String authorizeData = (String) params.get("authorize");
String appId = (String) params.get(MiniAppInfoConstants.MINI_APP_ID);
String userId = (String) params.get(MiniAppInfoConstants.USER_ID);
String miniAppInfo = (String) params.get("miniAppInfo");
if (miniAppInfo != null) {
try {
JSONObject jsonObject = new JSONObject(miniAppInfo);
String miniAppIcon = jsonObject.optString("miniAppIcon");
String miniAppName = jsonObject.optString("miniAppName");
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
Se o usuário cancelar a autorização, defina os dados no onComplete da seguinte maneira:
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData( UIResultCodeConstants.CANCEL));
Caso o usuário rejeite a autorização, configure os dados no onComplete assim:
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData( "User refuses authorization request", UIResultCodeConstants.ERROR));
Ao concordar com a autorização, o resultado retornará ao callback wv.authorize (marcando os escopos de entrada como true/false). Defina os dados no onComplete conforme abaixo:
/*
finalJsObj: The information format is as follows JsonObject:
{"location": true,"camera": false}
*Example:JSONObject finalJsObj=new JSONObject
*finalJsObj.put("file","true/false")
*/
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(finalJsObj, "", UIResultCodeConstants.CONFIRM));
c. Em UserMoreDialogFragment
O uiComponentParam.params possui a seguinte estrutura:
miniAppInfo: Metainformações do miniapp.
appId: ID do miniapp
userId: userId
miniAppUrl: URL da página atual do miniapp
/*This corresponds to the structure obtained by param in UIComponentParam
{
"miniAppUrl":"current page url"
"appId": appId,
"userId": userInfo.userId,
"miniAppInfo": miniAppInfo
}
This corresponds to the structure obtained according to the key 'miniAppInfo'
in param in UIComponentParam.
miniAppInfo:
{
"miniAppId": appId,
"miniAppName": appName,
"miniAppIcon": iconUrl,
"miniAppVersion": version,
"miniAppSlogan": slogan,
"miniAppDescription": description
}
*/
HashMap<String, Object> params = uiComponentParam.getParam();
String authorizeData = (String) params.get("miniAppUrl");
String appId = (String) params.get(MiniAppInfoConstants.MINI_APP_ID);
String userId = (String) params.get(MiniAppInfoConstants.USER_ID);
String miniAppInfo = (String) params.get("miniAppInfo");
if (miniAppInfo != null) {
try {
JSONObject jsonObject = new JSONObject(miniAppInfo);
String miniAppIcon = jsonObject.optString("miniAppIcon");
String miniAppName = jsonObject.optString("miniAppName");
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
-
Para ativar o botão Favoritos, implemente a interface "IMiniAppFavoriteService".
Código de exemplo:
public class MiniAppFavoriteServiceImpl implements IMiniAppFavoriteService {
public static List<MiniAppInfo> mFavorites = new ArrayList<>();
static {
getFav();
}
@Override
public void addToFavorites(String subAppCode, ServiceCallback callback) {
if (!isFavorite(subAppCode)) {
if (mFavorites.size() >= 7) {
if (callback != null) {
Result result = new Result();
result.success = false;
result.msg = "Collect up to 7 mini programs";
callback.onFinish(result);
return;
}
}
List<MiniAppInfo> miniApps = MiniAppInfoManager.getInstance().getCache();
if (miniApps == null || miniApps.isEmpty()) {
if (FlavorUtil.isVSCodeIDE()) {
Result result = new Result();
result.success = false;
callback.onFinish(result);
return;
}
if (callback != null) {
Result result = new Result();
result.success = false;
result.msg = "Collecting mini program failed";
callback.onFinish(result);
return;
}
}
for (MiniAppInfo data : miniApps) {
if (TextUtils.equals(subAppCode, data.getAppId())) {
mFavorites.add(data);
saveFav();
break;
}
}
}
if (callback != null) {
callback.onFinish(new Result());
}
}
@Override
public void removeFromFavorites(String subAppCode, ServiceCallback callback) {
if (isFavorite(subAppCode)) {
for (int i = 0; i != mFavorites.size(); ++i) {
if (TextUtils.equals(subAppCode, mFavorites.get(i).getAppId())) {
mFavorites.remove(i);
saveFav();
break;
}
}
}
if (callback != null) {
callback.onFinish(new Result());
}
}
@Override
public boolean isFavorite(String subAppCode) {
for (MiniAppInfo data : mFavorites) {
if (TextUtils.equals(subAppCode, data.getAppId())) {
return true;
}
}
return false;
}
private void saveFav() {
JSONArray jsonArray = new JSONArray();
for (MiniAppInfo data : mFavorites) {
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("appId", data.getAppId());
jsonObject.put("name", data.getAppName());
jsonObject.put("icon", data.getAppIcon());
jsonArray.put(jsonObject);
} catch (JSONException e) {
}
}
Context context = MainModuleService.context;
if (context != null) {
SharedPreferences sp = context.getSharedPreferences("mini_app", 0);
SharedPreferences.Editor editor = sp.edit();
editor.putString("mini_app_fav", jsonArray.toString());
editor.commit();
}
}
private static void getFav() {
Context context = MainModuleService.context;
if (context != null) {
String fav = sp.getString("mini_app_fav", null);
if (!TextUtils.isEmpty(fav)) {
try {
JSONArray jsonArray = new JSONArray(fav);
for (int i = 0; i != jsonArray.length(); ++i) {
JSONObject json = jsonArray.getJSONObject(i);
MiniAppInfo miniAppData = new MiniAppInfo();
miniAppData.setAppId(json.getString("appId"));
miniAppData.setAppName(json.getString("name"));
miniAppData.setAppIcon(json.getString("icon"));
mFavorites.add(miniAppData);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
O botão "setting" gerencia o escopo de informações do dispositivo do usuário e permite aprovar ou revogar permissões.
Ao clicar no botão "setting", a página UserInfoAuthPageFragment abre.
Nota: Para usar um escopo personalizado, personalize a página conforme descrito em 3.1.3.1.6 (AuthorizeSettingsFragment).
UserInfoAuthPageFragment:
Os parâmetros iniciais para abrir a página UserInfoAuthPageFragment são:
uiComponentParam.params
miniAppInfo: Metainformações do miniapp.
appId: ID do miniapp
userId: userId
O botão "copy" copia a URL da página atual.
Para habilitar o botão "share", implemente a interface "IMiniAppMoreService". Código de exemplo:
public class MyShare implements IMiniAppMoreService {
/**
* @param miniAppInfo
* miniAppInfoMap.put("miniAppId","miniAppId");
* miniAppInfoMap.put("miniAppName", "miniAppName");
* miniAppInfoMap.put("miniAppIcon", "miniAppIcon");
* miniAppInfoMap.put("miniAppSlogan", "miniAppSlogan");
* miniAppInfoMap.put("miniAppDescription", "miniAppDescription");
* miniAppInfoMap.put("miniAppVersion", "miniAppVersion");
*/
@Override
public String share(HashMap<String, String> miniAppInfo) {
return "emassuperapp://miniapp/startapp?appId=mini app ID";
}
}
// register the "share" function
ServiceManager.getInstance().registerService(IMiniAppMoreService.class.getName(), MyShare.class);
O botão "refresh" reinicia o miniapp.
Para abrir as páginas "about us" e "scope setting" na página personalizada de mais opções, utilize o código de exemplo abaixo:
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int id= v.getId();
if(id==about_us){
if (uiResultCallBack != null) {
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.OPEN_ABOUT));
}
}else if(id==auth_setting){
if (uiResultCallBack != null) {
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.OPEN_SETTING));
}
}
}
c. Em UserMoreDialogFragment
O uiComponentParam.params tem a seguinte estrutura:
miniAppInfo: Metainformações do miniapp.
appId: ID do miniapp
userId: userId
miniAppUrl: URL da página atual do miniapp
/*This corresponds to the structure obtained by param in UIComponentParam
{
"miniAppUrl":"current page url"
"appId": appId,
"userId": userInfo.userId,
"miniAppInfo": miniAppInfo
}
This corresponds to the structure obtained according to the key 'miniAppInfo'
in param in UIComponentParam.
miniAppInfo:
{
"miniAppId": appId,
"miniAppName": appName,
"miniAppIcon": iconUrl,
"miniAppVersion": version,
"miniAppSlogan": slogan,
"miniAppDescription": description
}
*/
HashMap<String, Object> params = uiComponentParam.getParam();
String authorizeData = (String) params.get("miniAppUrl");
String appId = (String) params.get(MiniAppInfoConstants.MINI_APP_ID);
String userId = (String) params.get(MiniAppInfoConstants.USER_ID);
String miniAppInfo = (String) params.get("miniAppInfo");
if (miniAppInfo != null) {
try {
JSONObject jsonObject = new JSONObject(miniAppInfo);
String miniAppIcon = jsonObject.optString("miniAppIcon");
String miniAppName = jsonObject.optString("miniAppName");
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
Para ativar o botão Favoritos, implemente a interface "IMiniAppFavoriteService".
Código de exemplo:
public class MiniAppFavoriteServiceImpl implements IMiniAppFavoriteService {
public static List<MiniAppInfo> mFavorites = new ArrayList<>();
static {
getFav();
}
@Override
public void addToFavorites(String subAppCode, ServiceCallback callback) {
if (!isFavorite(subAppCode)) {
if (mFavorites.size() >= 7) {
if (callback != null) {
Result result = new Result();
result.success = false;
result.msg = "Collect up to 7 mini programs";
callback.onFinish(result);
return;
}
}
List<MiniAppInfo> miniApps = MiniAppInfoManager.getInstance().getCache();
if (miniApps == null || miniApps.isEmpty()) {
if (FlavorUtil.isVSCodeIDE()) {
Result result = new Result();
result.success = false;
callback.onFinish(result);
return;
}
if (callback != null) {
Result result = new Result();
result.success = false;
result.msg = "Collecting mini program failed";
callback.onFinish(result);
return;
}
}
for (MiniAppInfo data : miniApps) {
if (TextUtils.equals(subAppCode, data.getAppId())) {
mFavorites.add(data);
saveFav();
break;
}
}
}
if (callback != null) {
callback.onFinish(new Result());
}
}
@Override
public void removeFromFavorites(String subAppCode, ServiceCallback callback) {
if (isFavorite(subAppCode)) {
for (int i = 0; i != mFavorites.size(); ++i) {
if (TextUtils.equals(subAppCode, mFavorites.get(i).getAppId())) {
mFavorites.remove(i);
saveFav();
break;
}
}
}
if (callback != null) {
callback.onFinish(new Result());
}
}
@Override
public boolean isFavorite(String subAppCode) {
for (MiniAppInfo data : mFavorites) {
if (TextUtils.equals(subAppCode, data.getAppId())) {
return true;
}
}
return false;
}
private void saveFav() {
JSONArray jsonArray = new JSONArray();
for (MiniAppInfo data : mFavorites) {
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("appId", data.getAppId());
jsonObject.put("name", data.getAppName());
jsonObject.put("icon", data.getAppIcon());
jsonArray.put(jsonObject);
} catch (JSONException e) {
}
}
Context context = MainModuleService.context;
if (context != null) {
SharedPreferences sp = context.getSharedPreferences("mini_app", 0);
SharedPreferences.Editor editor = sp.edit();
editor.putString("mini_app_fav", jsonArray.toString());
editor.commit();
}
}
private static void getFav() {
Context context = MainModuleService.context;
if (context != null) {
String fav = sp.getString("mini_app_fav", null);
if (!TextUtils.isEmpty(fav)) {
try {
JSONArray jsonArray = new JSONArray(fav);
for (int i = 0; i != jsonArray.length(); ++i) {
JSONObject json = jsonArray.getJSONObject(i);
MiniAppInfo miniAppData = new MiniAppInfo();
miniAppData.setAppId(json.getString("appId"));
miniAppData.setAppName(json.getString("name"));
miniAppData.setAppIcon(json.getString("icon"));
mFavorites.add(miniAppData);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
O botão "setting" gerencia o escopo de informações do dispositivo do usuário e permite aprovar ou revogar permissões.
Ao clicar no botão "setting", a página UserInfoAuthPageFragment abre.
Nota: Para usar um escopo personalizado, personalize a página conforme descrito em 3.1.3.1.6 (AuthorizeSettingsFragment).
UserInfoAuthPageFragment:
Os parâmetros iniciais para abrir a página UserInfoAuthPageFragment são:
uiComponentParam.params
miniAppInfo: Metainformações do miniapp.
appId: ID do miniapp
userId: userId
O botão "copy" copia a URL da página atual.
Para habilitar o botão "share", implemente a interface "IMiniAppMoreService". Código de exemplo:
public class MyShare implements IMiniAppMoreService {
/**
* @param miniAppInfo
* miniAppInfoMap.put("miniAppId","miniAppId");
* miniAppInfoMap.put("miniAppName", "miniAppName");
* miniAppInfoMap.put("miniAppIcon", "miniAppIcon");
* miniAppInfoMap.put("miniAppSlogan", "miniAppSlogan");
* miniAppInfoMap.put("miniAppDescription", "miniAppDescription");
* miniAppInfoMap.put("miniAppVersion", "miniAppVersion");
*/
@Override
public String share(HashMap<String, String> miniAppInfo) {
return "emassuperapp://miniapp/startapp?appId=mini app ID";
}
}
// register the "share" function
ServiceManager.getInstance().registerService(IMiniAppMoreService.class.getName(), MyShare.class);
O botão "refresh" reinicia o miniapp.
Para abrir as páginas "about us" e "scope setting" na página personalizada de mais opções, utilize o código de exemplo abaixo:
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int id= v.getId();
if(id==about_us){
if (uiResultCallBack != null) {
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.OPEN_ABOUT));
}
}else if(id==auth_setting){
if (uiResultCallBack != null) {
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.OPEN_SETTING));
}
}
}
d. Em user AboutUsDialogfragment
Código de exemplo: consulte 3.1.3.1.8.
O uiComponentParam.params contém:
miniAppInfo: Metainformações do miniapp.
e. Em user FavoriteDialogFragment
Código de exemplo: consulte 3.1.3.1.7.
O uiComponentParam.params inclui:
miniAppInfo: Metainformações do miniapp.
appId: ID do miniapp
userId: userId
3.1.2.2 No iOS
@protocol EMASIUIExtension <NSObject>
@optional
// Used to create 'More' dialog, refer to 'figure 1' in the remarks for example style
- (nullable UIViewController *)createMoreDialogViewController:(EMASUIComponentParam *)uiComponentParam;
// Used to create 'Favorite' dialog, refer to 'figure 2' in the remarks for example style
- (nullable UIViewController *)createFavoriteDialogViewController:(EMASUIComponentParam *)uiComponentParam;
// Used to create 'Auth' dialog, refer to 'figure 3' in the remarks for example style
- (nullable UIViewController *)createUserDeviceInfoAuthDialogViewController:(EMASUIComponentParam *)uiComponentParam;
// Used to create 'Error' sub page, refer to 'figure 5' in the remarks for example style
- (nullable UIViewController *)createErrorPageViewController:(EMASUIComponentParam *)uiComponentParam;
// Used to create 'Slash' sub page, refer to 'figure 6' in the remarks for example style
- (nullable UIViewController *)createSplashPageViewController:(EMASUIComponentParam *)uiComponentParam;
// Used to create 'About US' dialog, refer to 'figure 8' in the remarks for example style
- (nullable UIViewController *)createAboutUsDialogViewController:(EMASUIComponentParam *)uiComponentParam;
- (nullable UIViewController *)createSettingsDialogViewController:(EMASUIComponentParam *)uiComponentParam;
@end
@interface EMASUIComponentParam : NSObject
@property (nonatomic, strong) NSDictionary * params;
@property (nonatomic, copy) void(^completionBlock)(NSDictionary *resultDict);
@end
FOUNDATION_EXPORT NSString * _Nonnull const EMAS_CANCEL;
FOUNDATION_EXPORT NSString * _Nonnull const EMAS_CONFIRM;
FOUNDATION_EXPORT NSString * _Nonnull const EMAS_RESTART_MINIAPP;
FOUNDATION_EXPORT NSString * _Nonnull const EMAS_EXIT_MINIAPP;
FOUNDATION_EXPORT NSString * _Nonnull const EMAS_ERROR;
FOUNDATION_EXPORT NSString * _Nonnull const EMAS_OPEN_ABOUT;
FOUNDATION_EXPORT NSString * _Nonnull const EMAS_OPEN_SETTING;
Como usar constantes no formato de dados de autorização
EMAS_CANCEL: Quando o usuário clicar no botão Cancelar na página, defina o
codecom esta constante. O SDK fechará a janela pop-up.EMAS_CONFIRM: Ao clicar no botão Confirmar na página, defina o
codecom esta constante e retorne as informações relevantes emdata. O SDK fechará a janela pop-up.EMAS_ERROR: Se o processamento do componente falhar, defina o
codecom esta constante e forneça uma mensagem de erro emmsg. O SDK fechará a janela pop-up e passará a mensagem de erro ao JavaScript para retorno ao miniapp.EMAS_RESTART_MINIAPP: Caso o usuário clique no botão Atualizar/Reiniciar na página do miniapp, defina o
codecom esta constante. O SDK reabrirá e recarregará o miniapp.EMAS_EXIT_MINIAPP: Quando o usuário clicar no botão Sair na página do miniapp, defina o
codecom esta constante. O SDK fechará o miniapp atual.
Como usar o completionBlock e as constantes de código:
a. Em ErrorPageViewController:
Quando o usuário clicar no botão Sair na página do miniapp, defina o code como EMAS_EXIT_MINIAPP.
uiComponentParam.completionBlock(["code": EMAS_EXIT_MINIAPP])
Se o usuário clicar no botão Atualizar/Reiniciar na página do miniapp, defina o code como EMAS_RESTART_MINIAPP.
uiComponentParam.completionBlock(["code": EMAS_RESTART_MINIAPP])
b. Em UserDeviceInfoAuthDialogViewController:
O uiComponentParam.params tem a seguinte estrutura:
authorize: Este parâmetro é transmitido quando o miniapp chama wv.authorize. Quando o parâmetro do método wv.authorize contém tanto "scope" quanto "scopes", "scopes" tem prioridade. Caso contrário, utiliza-se o parâmetro "scope". Além disso, o SDK consultará o status de autorização concedido anteriormente pelo miniapp e definirá o status de autorização para os escopos solicitados.
appId: ID do miniapp
userId: userId
miniAppInfo: Metainformações do miniapp
/*
[
"authorize": ["location": true/false, "camera": true/false],
"appId": appId,
"userId": userInfo.userId,
"miniAppInfo": miniAppInfo
]
miniAppInfo:
[
"miniAppId": appId,
"miniAppName": appName,
"miniAppIcon": iconUrl,
"miniAppVersion": version,
"miniAppSlogan": slogan,
"miniAppDescription": description
]
*/
Se o usuário cancelar a autorização, defina os dados no completionBlock da seguinte maneira:
uiComponentParam.completionBlock(["code": EMAS_CANCEL])
Caso o usuário rejeite a autorização, configure os dados no completionBlock assim:
uiComponentParam.completionBlock(["code": EMAS_CANCEL, "msg":"User cancels authorization"])
Ao concordar com a autorização, o resultado retornará ao callback wv.authorize (marcando os escopos de entrada como true/false). Defina os dados no completionBlock conforme abaixo:
/*
authDic: The information format is as follows:
["location": true,"camera": false]
*/
uiComponentParam.completionBlock(["code": EMAS_CONFIRM, "msg":"", "data":authDic])
c. Em UserMoreDialogFragment
O uiComponentParam.params possui a seguinte estrutura:
miniAppId: ID do miniapp
userId: userId
miniAppUrl: URL da página atual do miniapp
miniAppName: appName
miniAppIcon: iconUrl
miniAppSlogan: slogan
miniAppDescription: description
miniAppVersion: version
/*
This corresponds to the structure obtained in param in UIComponentParam.
{
"miniAppId": appId,
"miniAppName": appName,
"miniAppIcon": iconUrl,
"miniAppVersion": version,
"miniAppSlogan": slogan,
"miniAppDescription": description,
"miniAppUrl": "current page url",
"userId": userId
}
*/
Para ativar o botão Favoritos, implemente EMASMiniAppFavoriteService.
Ative a função de favoritos no diálogo Mais:
#import "EMASMiniAppFavoriteServiceImpl.h"
@implementation EMASMiniAppFavoriteServiceImpl
/**
* add to favorites
* @param subAppCode miniapp id
* @return int 0, add success | not 0, error code
*/
- (void)addToFavorites:(NSString*)subAppCode completion:(nonnull EMASFavoriteCompletionBlock)completionBlock{
[[NSUserDefaults standardUserDefaults] setObject:@(YES) forKey:subAppCode];
[[NSUserDefaults standardUserDefaults] synchronize];
if (completionBlock) {
completionBlock(@{});
}
}
/**
* remove from favorites
* @param subAppCode miniapp id
* @return int 0, add success | not 0, error code
*/
- (void)removeFromFavorites:(NSString*)subAppCode completion:(nonnull EMASFavoriteCompletionBlock)completionBlock{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:subAppCode];
[[NSUserDefaults standardUserDefaults] synchronize];
if (completionBlock) {
completionBlock(@{});
}
}
/**
* is favorite
* @param subAppCode miniapp id
* @return int 0, add success | not 0, error code
*/
- (BOOL)isFavorite:(NSString*)subAppCode {
BOOL isFavorite = [[NSUserDefaults standardUserDefaults] objectForKey:subAppCode];
return isFavorite;
}
@end
O botão "setting" gerencia o escopo de informações do dispositivo do usuário e permite aprovar ou revogar permissões.
Ao clicar no botão "setting", a página SettingViewController abre.
Nota: Para usar um escopo personalizado, personalize a página conforme descrito em 3.1.3.2.6 (CPSettingPage).
Os parâmetros iniciais para abrir a página SettingViewController são:
O uiComponentParam.params contém:
miniAppId: ID do miniapp
userId: userId
miniAppUrl: URL da página atual do miniapp
miniAppName: appName
miniAppIcon: iconUrl
miniAppSlogan: slogan
miniAppDescription: description
miniAppVersion: version
O botão "copy" copia a URL da página atual.
Para habilitar o botão "share", implemente o protocolo EMASMiniAppMoreService. Código de exemplo:
import Foundation
@objc(EMASMiniAppMoreServiceImpl)
class EMASMiniAppMoreServiceImpl: NSObject, EMASMiniAppMoreService {
/*
*@param miniAppInfo
{
"miniAppId": appId,
"miniAppName": appName,
"miniAppIcon": iconUrl,
"miniAppVersion": version,
"miniAppSlogan": slogan,
"miniAppDescription": description,
"userId": userId
}
*/
@objc func share(_ miniAppInfo: [AnyHashable : Any]!) -> String! {
let appid: String = miniAppInfo["miniAppId"] as? String ?? ""
return String(format: "%@%@", "superapp://miniapp/startapp?appId=", appid)
}
}
O botão "refresh" reinicia o miniapp.
Implemente o protocolo EMASMiniAppMoreService se o MiniApp for executado em uma janela separada.
import Foundation
import EMASMiniApp
import EMASServiceManager
@objc(EMASMiniAppMoreServiceImpl)
class EMASMiniAppMoreServiceImpl: NSObject, EMASMiniAppMoreService {
@objc func restartMiniApp(_ miniAppId: String!, openConfiguration: EMASMiniAppOpenConfiguration!) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
if let miniAppService = EMASServiceManager.sharedInstance().service(forProtocol: "EMASMiniAppService") as? EMASMiniAppService {
//TODO:switch window visible,and call openMiniApp method, such as:
let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate?.window?.isHidden = true;
appDelegate?.windvaneWindow?.isHidden = false
appDelegate?.windvaneWindow?.makeKeyAndVisible()
miniAppService.openMiniApp(miniAppId, openConfiguration: openConfiguration) { code, resultDic in
}
}
}
}
}
Para abrir as páginas "about us" e "scope setting" na página personalizada de mais opções, utilize o código de exemplo abaixo:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
NSString *appId = self.miniAppInfo[@"miniAppId"] ?: @"";
NSString * code = @"";
switch (item.key) {
//Add to my Favorites
case MenuItemKeyAddToFav:
{
code = EMAS_CANCEL;
id<EMASMiniAppFavoriteService> favService = [[EMASServiceManager sharedInstance] serviceForProtocol:@"EMASMiniAppFavoriteService"];
if (favService){
BOOL isFavorite = [favService isFavorite:appId];
__weak typeof(self) weakSelf = self;
if (!isFavorite) {
[favService addToFavorites:appId completion:^(NSDictionary * _Nonnull result) {
[CPCommonTool showToast:kLocalStringWithValue(@"miniapp_more_favorite_success", @"Added to favorites")];//miniapp_more_favorite_success
}];
}else{
if(favService){
[favService removeFromFavorites:appId completion:^(NSDictionary * _Nonnull result) {
[CPCommonTool showToast:kLocalStringWithValue(@"miniapp_more_remove_favorite_success", @"Removed from my favorites")];
}];
}
}
};
if (self.completionBlock && code && code .length != 0) {
self.completionBlock(@{@"code":code});
}
}
break;
//Settings
case MenuItemKeySettings:
{
//Navigate to the settings page
code = EMAS_OPEN_SETTING;
if (self.completionBlock && code && code .length != 0) {
self.completionBlock(@{@"code":code});
}
}
break;
//Copy Link
case MenuItemKeyCopyLink:
{
NSString *urlStr = self.miniAppInfo[@"miniAppUrl"];
if (urlStr && ![urlStr isEqualToString:@""]) {
UIPasteboard * pasteboard = [UIPasteboard generalPasteboard];
pasteboard.string = urlStr;
[CPCommonTool showToast:kLocalStringWithValue(@"miniapp_more_copy_success", @"Copied")];
}
}
break;
//Share
case MenuItemKeyShare:
{
__weak typeof(self) weakself = self;
UIViewController* vc = self.presentingViewController;
NSString* url = self.miniAppInfo[@"miniAppUrl"];
[self dismissViewControllerWithCompletion:^{
__strong typeof(weakself) strongSelf = weakself;
strongSelf.completionBlock(@{@"code":EMAS_CANCEL});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongSelf showShareWithString:url showIn:vc];
});
}];
}
break;
//About Us
case MenuItemKeyAboutUs:
{
code = EMAS_OPEN_ABOUT;
if (self.completionBlock && code && code .length != 0) {
self.completionBlock(@{@"code":code});
}
}
break;
//Refresh
case MenuItemKeyRefresh:
{
code = EMAS_RESTART_MINIAPP;
if (self.completionBlock && code && code .length != 0) {
self.completionBlock(@{@"code":code});
}
}
default:
break;
}
}
d. Em user AboutUsDialogfragment
O uiComponentParam.params inclui:
miniAppId: ID do miniapp
userId: userId
miniAppUrl: URL da página atual do miniapp
miniAppName: appName
miniAppIcon: iconUrl
miniAppSlogan: slogan
miniAppDescription: description
miniAppVersion: version.
e. Em user FavoriteDialogFragment
O uiComponentParam.params tem a seguinte estrutura:
miniAppId: ID do miniapp
userId: userId
miniAppUrl: URL da página atual do miniapp
miniAppName: appName
miniAppIcon: iconUrl
miniAppSlogan: slogan
miniAppDescription: description
miniAppVersion: version
3.1.3 Implementar interfaces via plugin
3.1.3.1 Implementação para Android
1. Criar UIPlugin personalizado no cliente
public class CustomUIPlugin extends BasePlugin implements UIExtension {
public CustomUIPlugin(Context context) {
super(context);
}
@Override
public Fragment createSplashPageFragment(UIComponentParam uiComponentParam) {
return SplashPageFragment.newInstance();
}
@Override
public Fragment createUserDeviceInfoAuthDialogFragment(UIComponentParam uiComponentParam) {
/* This is the data we package
HashMap<String, Object> hashMap = new HashMap();
hashMap.put("authorize", new JSONObject(authorize).toString());
hashMap.put("miniAppInfo", getMiniAppInfo().toString());
hashMap.put(MiniAppInfoConstants.MINI_APP_ID, appId);
hashMap.put(MiniAppInfoConstants.USER_ID, userInfo.getUserId()); */
HashMap<String, Object> params = uiComponentParam.getData();
//Here you can do your authorization logic to verify when a pop-up window is needed. If no pop-up window is needed,
//you can return null.
if (uiComponentParam == null) {
return null;
}
if (uiComponentParam.getData() == null) return null;
HashMap<String, Object> params = uiComponentParam.getData();
// boolean permissionValid = MiniAppAuthorizations.isPermissionValid(innerScope);
String authorizeData = (String) params.get("authorize");
String appId = (String) params.get(MiniAppInfoConstants.MINI_APP_ID);
String userId = (String) params.get(MiniAppInfoConstants.USER_ID);
JSONObject jsObj = null;
boolean permissionValid = true;
List<String> authorize = new ArrayList<>();
JSONObject returnData = new JSONObject();
JSONObject authorizedData = new JSONObject();
String authorizeValid = "";
List<String> authorizedValid = new ArrayList<>();
try {
jsObj = new JSONObject(authorizeData);
if (jsObj != null) {
Iterator<String> keys = jsObj.keys();
for (Iterator<String> it = keys; it.hasNext(); ) {
String key = it.next();
Boolean value = (Boolean) jsObj.get(key);
permissionValid = MiniAppAuthorizations.isPermissionValid(key);
if (permissionValid) {
if (!value) {
authorize.add(key);
} else {
authorizedData.put(key, true);
}
} else {
authorizedValid.add(key);
authorizeValid += "'" + key + "',";
}
}
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
if (authorize.size() == 0 && authorizedData.length() == 0) {
if (uiComponentParam.getUiResultCallBack() != null) {
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(null, "Error: Unknown permission " + authorizeValid.substring(0, authorizeValid.length() - 1) + " ", UIResultCallBackConstants.CANCEL_BTN));
}
return null;
} else if (authorize.size() == 0 && authorizedData.length() != 0) {
if (uiComponentParam.getUiResultCallBack() != null) {
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(authorizedData, "", UIResultCallBackConstants.SURE_BTN));
}
return null;
}
params.put("authorize", authorize);
JSONObject finalJsObj = jsObj;
String finalAuthorizeValid = authorizeValid;
return return AuthorizeFragment.newInstance(params, new OnDialogButtonClickListener() {
@Override
public void onPositiveClick(String data) {
//This data can be the data that the user chooses to authorize. Of course, you can also choose to save the logic processing when you click the OK button,
//or you can return the data for processing.
IAuthService authService = PluginEnv.getInstance().getPluginContext().getContainer().getAuthService();
JSONArray restoredList = null;
if (!TextUtils.isEmpty(data)) {
try {
restoredList = new JSONArray(data);
JSONObject jsonObject = new JSONObject();
if (restoredList != null && restoredList.length() > 0) {
for (int i = 0; i < restoredList.length(); i++) {
String innerScope = restoredList.getString(i);
//
if (authService == null) {
} else {
authService.saveAuth(uiComponentParam.getContext(), innerScope, userId, appId);
finalJsObj.put(innerScope, true);
}
}
if (authorizedValid.size() != 0) {
for (String authorize : authorizedValid) {
finalJsObj.remove(authorize);
}
}
}
//finalJsObj: This is the data we need from you to confirm successful authorization.
// Format : new JSONObject ,finalJsObj.put("file", true); {"file":true,"location":true}
if (uiComponentParam.getUiResultCallBack() != null) {
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(finalJsObj, "", UIResultCallBackConstants.SURE_BTN));
//
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void onNegativeClick(String data) {
if (uiComponentParam.getUiResultCallBack() != null) {
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(null, "User refuses authorization request", UIResultCallBackConstants.CANCEL_BTN));
}
}
}, uiComponentParam.getContext());
}
HashMap<String, Object> params = uiComponentParam.getParams();
String authorizeData = (String) params.get("authorize");
String appId = (String) params.get(MiniAppInfoConstants.MINI_APP_ID);
String userId = (String) params.get(MiniAppInfoConstants.USER_ID);
JSONObject jsObj = null;
boolean permissionValid = true;
List<String> authorize = new ArrayList<>();
JSONObject authorizedData = new JSONObject();
String authorizeValid = "";
List<String> authorizedValid = new ArrayList<>();
try {
jsObj = new JSONObject(authorizeData);
if (jsObj != null) {
Iterator<String> keys = jsObj.keys();
for (Iterator<String> it = keys; it.hasNext(); ) {
String key = it.next();
Boolean value = (Boolean) jsObj.get(key);
permissionValid = MiniAppAuthorizations.isPermissionValid(key);
if (permissionValid) {
if (!value) {
authorize.add(key);
} else {
authorizedData.put(key, true);
}
} else {
authorizedValid.add(key);
authorizeValid += "'" + key + "',";
}
}
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
if (authorize.size() == 0 && authorizedData.length() == 0) {
if (uiComponentParam.getUiResultCallBack() != null) {
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData("Error: Unknown permission " + authorizeValid.substring(0, authorizeValid.length() - 1) + " ", UIResultCodeConstants.ERROR));
}
return null;
} else if (authorize.size() == 0 && authorizedData.length() != 0) {
if (uiComponentParam.getUiResultCallBack() != null) {
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(authorizedData, "", UIResultCodeConstants.CONFIRM));
}
return null;
}
params.put("authorize", authorize);
JSONObject finalJsObj = jsObj;
return AuthorizeFragment.newInstance(params, new OnDialogButtonClickListener() {
@Override
public void onPositiveClick(String data) {
IAuthService authService = PluginEnv.getInstance().getPluginContext().getContainer().getAuthService();
JSONArray restoredList = null;
if (!TextUtils.isEmpty(data)) {
try {
restoredList = new JSONArray(data);
if (restoredList != null && restoredList.length() > 0) {
for (int i = 0; i < restoredList.length(); i++) {
String innerScope = restoredList.getString(i);
if (authService != null) {
authService.saveAuth(uiComponentParam.getContext(), innerScope, userId, appId);
finalJsObj.put(innerScope, true);
}
}
if (authorizedValid.size() != 0) {
for (String authorize : authorizedValid) {
finalJsObj.remove(authorize);
}
}
}
if (uiComponentParam.getUiResultCallBack() != null) {
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(finalJsObj, "", UIResultCodeConstants.CONFIRM));
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void onNegativeClick(String data) {
if (uiComponentParam.getUiResultCallBack() != null) {
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData("User refuses authorization request", UIResultCodeConstants.ERROR));
}
}
}, uiComponentParam.getContext());
}
@Override
public Fragment createErrorPageFragment(UIComponentParam uiComponentParam) {
/* This is the data we package
* HashMap<String, Object> map = new HashMap<>();
* map.put("errorCode", 1);
*/
return ErrorFragment.newInstance(uiComponentParam);
}
...
}
2. Implementar UserDeviceInfoAuthDialogFragment
package com.alibaba.plugin.ui.fragment;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.base.AbstractDialogFragment;
import com.alibaba.plugin.ui.R;
//If you are a native Android developer and you use a pop-up window,
//you can extends DialogFragment.
//If you are a flutter developer,
//you can extends Fragment.
public class AuthorizeFragment extends Fragment {
private FragmentAuthorizeBinding binding;
private TextView mCloseBtn;
private String miniAppInfo = null;
private List<String> itemList = new ArrayList<>();
private OnDialogButtonClickListener listener;
public AuthorizeFragment() {
// Required empty public constructor
}
//This is the miniinfo information we passed in.
// You can parse it according to the key we passed in.
// JSONObject miniInfoJson = new JSONObject();
// MiniAppInfo appInfo = updateData.miniAppInfo;
// miniInfoJson.put("miniAppName", appInfo.getAppName());
// miniInfoJson.put("miniAppId", updateData.appId);
// miniInfoJson.put("miniAppSlogan", appInfo.getAppSlogan());
// miniInfoJson.put("miniAppDescription", appInfo.getAppDesc());
// miniInfoJson.put("miniAppIcon", appInfo.getAppIcon());
// miniInfoJson.put("miniAppVersion", updateData.version);
// miniInfoJson.toString()
public static AuthorizeFragment newInstance(HashMap<String, Object> data, OnDialogButtonClickListener buttonClickListener, Context context) {
AuthorizeFragment fragment = new AuthorizeFragment();
fragment.itemList = (List<String>) data.get("authorize");
fragment.miniAppInfo = (String) data.get("miniAppInfo");
fragment.listener = buttonClickListener;
fragment.context = context;
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
binding = FragmentAuthorizeBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//Here you can do your logic operations and return data back
if (miniAppInfo != null) {
try {
JSONObject jsonObject = new JSONObject(miniAppInfo);
String miniAppIcon = jsonObject.optString("miniAppIcon");
String miniAppName = jsonObject.optString("miniAppName");
Picasso.get().load(miniAppIcon).placeholder(R.drawable.splash_logo).error(R.drawable.splash_logo).into(binding.headerIcon);
binding.headerTitle.setText(miniAppName + " " + getString(R.string.mini_app_authorize_apply));
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
binding.cancelButton.setOnClickListener(v -> {
listener.onNegativeClick(null);
dismiss();
});
binding.confirmButton.setOnClickListener(v -> {
if (itemList.size() == 1) {
listener.onPositiveClick(itemList.toString());
} else {
List<String> checkedAuthorized = new ArrayList<>();
for (int i = 0; i < binding.recyclerView.getChildCount(); i++) {
View child = binding.recyclerView.getChildAt(i);
CheckBox checkBox = child.findViewById(R.id.checkBox);
if (checkBox.isChecked()) {
int position = binding.recyclerView.getChildAdapterPosition(child);
String selectedItem = itemList.get(position);
checkedAuthorized.add(selectedItem);
}
}
if (checkedAuthorized.size() != 0) {
listener.onPositiveClick(checkedAuthorized.toString());
} else {
listener.onNegativeClick(null);
}
}
dismiss();
});
}
}
3. Implementar ErrorPageFragment
public class ErrorFragment extends Fragment {
private FragmentErrorBinding binding;
private static int errorCode = 0;
private static UIResultCallBack uiResultCallBack;
public static ErrorFragment newInstance(UIComponentParam uiComponentParam) {
if (uiComponentParam != null && uiComponentParam.getParams() != null) {
HashMap<String, Object> data = uiComponentParam.getParams();
errorCode = (int) data.get("errorCode");
uiResultCallBack = uiComponentParam.getUiResultCallBack();
}
return new ErrorFragment();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentErrorBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setText(binding.tvErrorMessage, ErrorConstants.getErrorMessage(getContext(), errorCode));
setText(binding.tvErrorCode, getString(R.string.mini_app_error_code) + ":00000" + ErrorConstants.getErrorCode(errorCode));
setLocalImage(binding.ivMiniAppLogo, ErrorConstants.getErrorIcon(errorCode));
setViewsClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int id = view.getId();
if (id == R.id.btn_exit) {
uiResultCallBack.onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.EXIT_MINIAPP));
} else if (id == R.id.btn_close) {
uiResultCallBack.onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.EXIT_MINIAPP));
} else if (id == R.id.btn_refresh) {
if (errorCode != MiniAppConstants.ERROR_PAGE_NOT_EXITS) {
if (uiResultCallBack != null) {
uiResultCallBack.onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.RESTART_MINIAPP));
}
}
}
}
}, binding.btnExit, binding.btnClose, binding.btnRefresh);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
protected void renderState(MiniAppState state) {
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
4. Implementar SplashPageFragment
//If you want to customize the splash screen when the applet is opened,
//you can refer to
public class SplashPageFragment extends Fragment implements ISplashExtension {
private FragmentLoadingBinding binding;
public static SplashPageFragment newInstance() {
return new SplashPageFragment();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentLoadingBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setImage(binding.ivLoadingIcon, appUrl);
setText(binding.tvLoadingName, appName);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
@Override
public void setProgress(int progress) {
//Here we can return to you the progress of downloading the mini program
binding.progressBar.setProgress(progress);
binding.statusText.setText(progress+"%" );
}
@Override
public void setMiniAppInfo(HashMap<String, Object> params) {
//Here you can update your interface to display mini program information if necessary
String miniAppInfo = (String) params.get("miniAppInfo");
try {
JSONObject jsonObject = new JSONObject(miniAppInfo);
String miniAppName = jsonObject.optString("miniAppName");
String miniAppIcon = jsonObject.optString("miniAppIcon");
if (TextUtils.isEmpty(appName)) {
setText(binding.tvLoadingName, miniAppName);
}
if (TextUtils.isEmpty(appUrl)) {
setImage(binding.ivLoadingIcon, miniAppIcon);
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}
5. Implementar MoreFragment
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.alibaba.emas.android.mini.app.common.log.MiniAppLog;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.R;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.data.MoreMenuItem;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.databinding.FragmentMoreBinding;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.databinding.MoreItemGridBinding;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.environment.PluginEnv;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.service.IMiniAppMoreService;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.service.IMoreService;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.misc.UIComponentParam;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.misc.UIResultCodeConstants;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.misc.UIResultDataUtil;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.protocol.UIResultCallBack;
import com.alibaba.module.android.core.servicebus.service.ServiceManager;
import com.alibaba.module.android.mini.app.api.IMiniAppFavoriteService;
import com.alibaba.module.android.mini.app.api.IToastService;
import com.squareup.picasso.Picasso;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class MoreFragment extends BaseDialogFragment {
private static final String TAG = "MoreFragment";
private FragmentMoreBinding binding;
private UIComponentParam uiComponentParam;
private JSONObject miniAppInfo = null;
private UIResultCallBack uiResultCallBack = null;
private String currentUrl = null;
private List<MoreMenuItem> moreMenuItems = new ArrayList<>();
public static MoreFragment newInstance(UIComponentParam uiComponentParam) {
MoreFragment fragment = new MoreFragment();
fragment.uiComponentParam = uiComponentParam;
return fragment;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Dialog dialog = new Dialog(requireContext(), R.style.BottomDialogTheme);
binding = FragmentMoreBinding.inflate(getLayoutInflater());
View contentView = binding.getRoot();
dialog.setContentView(contentView);
Window window = dialog.getWindow();
if (window != null) {
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
window.setGravity(Gravity.BOTTOM);
window.getAttributes().windowAnimations = R.style.mini_app_bottom_dialog_anim;
}
dialog.setCanceledOnTouchOutside(false);
dialog.setCancelable(false);
return dialog;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dealData();
}
private void dealData() {
if (uiComponentParam != null) {
HashMap<String, Object> params = uiComponentParam.getParams();
if (params != null) {
try {
miniAppInfo = TextUtils.isEmpty((String) params.get("miniAppInfo")) ? null : new JSONObject((String) params.get("miniAppInfo"));
currentUrl = (String) params.get("miniAppUrl");
} catch (JSONException e) {
MiniAppLog.d(TAG, "JSONException in dealData ");
}
}
uiResultCallBack = uiComponentParam.getUiResultCallBack();
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
binding = FragmentMoreBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setView();
setData();
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 4);
binding.rlMore.setLayoutManager(layoutManager);
GridAdapter gridAdapter = new GridAdapter(moreMenuItems);
binding.rlMore.setAdapter(gridAdapter);
}
private void setView() {
if (miniAppInfo != null) {
binding.headerTitle.setText(miniAppInfo.optString("miniAppName"));
setImage(binding.headerIcon, miniAppInfo.optString("miniAppIcon"));
}
binding.btnMoreCancel.setOnClickListener(view -> {
if (uiResultCallBack != null) {
uiResultCallBack.onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.CANCEL));
} else {
dismiss();
}
});
}
private void handleCallBackResultData(String resultData) {
if (uiResultCallBack != null) {
uiResultCallBack.onComplete(UIResultDataUtil.getResultData(resultData));
} else {
dismiss();
}
}
private void favorite(boolean isFavorite, IMoreService moreService) {
final IToastService toastService = ServiceManager.getInstance().getService(IToastService.class.getName());
if (isFavorite) {
moreService.removeFromFavorites(miniAppInfo.optString("miniAppId"), getActivity());
if (toastService == null) {
getActivity().runOnUiThread(() -> {
Toast.makeText(getActivity(), getString(R.string.mini_app_favorite_remove_success), Toast.LENGTH_SHORT).show();
});
}
} else {
moreService.addToFavorites(miniAppInfo.optString("miniAppId"), getActivity());
if (toastService == null) {
getActivity().runOnUiThread(() -> {
Toast.makeText(getActivity(), getString(R.string.mini_app_favorite_success), Toast.LENGTH_SHORT).show();
});
}
}
handleCallBackResultData(UIResultCodeConstants.CANCEL);
}
private void copy() {
ClipboardManager clipboardManager = (ClipboardManager) getContext()
.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText(currentUrl, currentUrl);
clipboardManager.setPrimaryClip(clipData);
getActivity().runOnUiThread(() -> {
if (getActivity() != null && !getActivity().isFinishing()) {
Toast toast = Toast.makeText(getActivity(), getString(R.string.mini_app_copy), Toast.LENGTH_SHORT);
toast.setGravity(Gravity.BOTTOM, 0, 0);
toast.show();
}
});
handleCallBackResultData(UIResultCodeConstants.CANCEL);
}
private void share(String share) {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, share);
shareIntent.putExtra(Intent.EXTRA_SUBJECT, "");
startActivity(Intent.createChooser(shareIntent, ""));
handleCallBackResultData(UIResultCodeConstants.CANCEL);
}
private void setData() {
moreMenuItems.clear();
IMoreService moreService = PluginEnv.getInstance().getPluginContext().getContainer().getMoreService();
IMiniAppFavoriteService miniAppFavoriteService = ServiceManager.getInstance().getService(IMiniAppFavoriteService.class.getName());
if (moreService != null && miniAppFavoriteService != null) {
boolean isFavorite = moreService.isFavorite(miniAppInfo.optString("miniAppId"));
String title = isFavorite ? getString(R.string.mini_app_more_remove_favorite)
: getString(R.string.mini_app_more_favorite);
int icon = isFavorite ? R.drawable.mini_app_remove_favorite
: R.drawable.mini_app_add_favorite;
moreMenuItems.add(new MoreMenuItem(title, icon, () -> {
favorite(isFavorite, moreService);
}));
}
if (!TextUtils.isEmpty(currentUrl)) {
moreMenuItems.add(new MoreMenuItem(getString(R.string.mini_app_more_settings),
R.drawable.mini_app_setting,
() -> handleCallBackResultData(UIResultCodeConstants.OPEN_SETTING)));
}
if (!TextUtils.isEmpty(currentUrl)) {
moreMenuItems.add(new MoreMenuItem(getString(R.string.mini_app_more_copy),
R.drawable.mini_app_copy,
() -> {
copy();
}));
}
IMiniAppMoreService service = ServiceManager.getInstance().getService(IMiniAppMoreService.class.getName());
HashMap<String, String> miniAppInfoMap = new HashMap<>();
if (miniAppInfo != null) {
miniAppInfoMap.put("miniAppId", miniAppInfo.optString("miniAppId"));
miniAppInfoMap.put("miniAppName", miniAppInfo.optString("miniAppName"));
miniAppInfoMap.put("miniAppIcon", miniAppInfo.optString("miniAppIcon"));
miniAppInfoMap.put("miniAppSlogan", miniAppInfo.optString("miniAppSlogan"));
miniAppInfoMap.put("miniAppDescription", miniAppInfo.optString("miniAppDescription"));
miniAppInfoMap.put("miniAppVersion", miniAppInfo.optString("miniAppVersion"));
}
if (service != null) {
String share = service.share(miniAppInfoMap);
moreMenuItems.add(new MoreMenuItem(getString(R.string.mini_app_more_Share),
R.drawable.mini_app_share, () -> {
share(share);
}));
}
moreMenuItems.add(new MoreMenuItem(getString(R.string.mini_app_more_refresh),
R.drawable.mini_app_refresh,
() -> handleCallBackResultData(UIResultCodeConstants.RESTART_MINIAPP)));
moreMenuItems.add(new MoreMenuItem(getString(R.string.mini_app_more_about_us),
R.drawable.mini_app_more_about_us,
() -> handleCallBackResultData(UIResultCodeConstants.OPEN_ABOUT)));
}
@Override
public void onStart() {
super.onStart();
Dialog dialog = getDialog();
if (dialog != null && dialog.getWindow() != null) {
Window window = dialog.getWindow();
window.setGravity(Gravity.BOTTOM);
window.getAttributes().windowAnimations = R.style.mini_app_bottom_dialog_anim;
}
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
class GridAdapter extends RecyclerView.Adapter<GridAdapter.ViewHolder> {
private List<MoreMenuItem> itemList;
public GridAdapter(List<MoreMenuItem> itemList) {
this.itemList = itemList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
MoreItemGridBinding moreItemGridBinding = MoreItemGridBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(moreItemGridBinding);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, @SuppressLint("RecyclerView") int position) {
MoreMenuItem item = itemList.get(position);
holder.itemGridBinding.tvMoreName.setText(item.title);
// Picasso.get().load(item.iconResId).placeholder(R.drawable.mini_app_default_logo).error(R.drawable.mini_app_default_logo).into(holder.itemGridBinding.tvMoreIcon);
setImage(holder.itemGridBinding.tvMoreIcon, item.iconResId);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
item.action.run();
}
});
}
@Override
public int getItemCount() {
return itemList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
MoreItemGridBinding itemGridBinding;
public ViewHolder(@NonNull MoreItemGridBinding binding) {
super(binding.getRoot());
this.itemGridBinding = binding;
}
}
}
}
6. Implementar UserInfoPageFragment
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.alibaba.emas.android.mini.app.common.log.MiniAppLog;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.R;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.databinding.FragmentAuthorizeSettingItemBinding;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.databinding.FragmentAuthorizeSettingsBinding;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.util.AuthorizeMessageUtil;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.util.DividerItemDecoration;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.environment.PluginEnv;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.service.IAuthService;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.misc.UIComponentParam;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.misc.UIResultCodeConstants;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.misc.UIResultDataUtil;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.protocol.UIResultCallBack;
import com.alibaba.module.android.mini.app.api.MiniAppInfoConstants;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.List;
public class AuthorizeSettingsFragment extends Fragment {
private static final String TAG = "AuthorizeSettingsFragment";
private FragmentAuthorizeSettingsBinding binding;
private ItemAdapter itemAdapter;
private String authorizeData = null;
private UIComponentParam uiComponentParam = null;
private Context context = null;
private String userId = null;
private String appId = null;
private List<String> authorizeTotalData = null;
private String miniAppName = "MiniApp Demo";
public static AuthorizeSettingsFragment newInstance(UIComponentParam uiComponentParam) {
AuthorizeSettingsFragment fragment = new AuthorizeSettingsFragment();
fragment.uiComponentParam = uiComponentParam;
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (uiComponentParam != null) {
HashMap<String, Object> params = uiComponentParam.getParams();
userId = (String) params.get(MiniAppInfoConstants.USER_ID);
appId = (String) params.get(MiniAppInfoConstants.MINI_APP_ID);
context = uiComponentParam.getContext();
miniAppName = (String) params.get(MiniAppInfoConstants.MINI_APP_NAME);
IAuthService authService = PluginEnv.getInstance().getContainerContext().getAuthService();
authorizeData = authService.queryAuthInfo(context, userId, appId);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
binding = FragmentAuthorizeSettingsBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.tvAuthorizeSetting.setText(getString(R.string.mini_app_more_authorize_setting_des) + miniAppName + getString(R.string.mini_app_more_authorize_setting_des_end));
setupRecyclerView();
binding.flMiniAppNavigationBarBack.setOnClickListener(view1 -> {
UIResultCallBack uiResultCallBack = uiComponentParam.getUiResultCallBack();
if (uiResultCallBack != null) {
uiResultCallBack.onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.CANCEL));
}
});
}
private void setupRecyclerView() {
authorizeTotalData = AuthorizeMessageUtil.getAuthorizeData();
itemAdapter = new AuthorizeSettingsFragment.ItemAdapter(authorizeTotalData);
binding.rlAuthorizeSetting.setLayoutManager(new LinearLayoutManager(getContext()));
binding.rlAuthorizeSetting.addItemDecoration(new DividerItemDecoration(2, Color.parseColor("#EEEEEE")));
binding.rlAuthorizeSetting.setAdapter(itemAdapter);
}
@Override
public void onStart() {
super.onStart();
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
private class ItemAdapter extends RecyclerView.Adapter<AuthorizeSettingsFragment.ItemAdapter.ViewHolder> {
private List<String> items;
public ItemAdapter(List<String> items) {
this.items = items;
}
@NonNull
@Override
public AuthorizeSettingsFragment.ItemAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
FragmentAuthorizeSettingItemBinding itemBinding = FragmentAuthorizeSettingItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new AuthorizeSettingsFragment.ItemAdapter.ViewHolder(itemBinding);
}
@Override
public void onBindViewHolder(@NonNull AuthorizeSettingsFragment.ItemAdapter.ViewHolder holder, int position) {
String authorize = items.get(position);
holder.binding.tvAuthorizeName.setText(AuthorizeMessageUtil.getAuthorizeName(authorize, getContext()));
try {
JSONObject jsonAuthorize = new JSONObject(authorizeData);
holder.binding.cbAuthorize.setChecked(jsonAuthorize.optBoolean(authorize, false));
} catch (JSONException e) {
MiniAppLog.d(TAG,"json pares error ");
}
holder.binding.cbAuthorize.setOnCheckedChangeListener((compoundButton, isChecked) -> {
IAuthService authService = PluginEnv.getInstance().getContainerContext().getAuthService();
if (isChecked) {
if (authorize != null) {
boolean b = authService.saveAuth(context, authorizeTotalData.get(position), userId, appId);
if (!b) {
holder.binding.cbAuthorize.setChecked(false);
}
}
} else {
if (authorize != null) {
boolean b = authService.revokeAuth(context, authorizeTotalData.get(position), userId, appId);
if (!b) {
holder.binding.cbAuthorize.setChecked(true);
}
}
}
});
}
@Override
public int getItemCount() {
return items.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
FragmentAuthorizeSettingItemBinding binding;
public ViewHolder(FragmentAuthorizeSettingItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
}
}
7. Implementar FavoriteFragment
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.R;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.databinding.FragmentAboutUsBinding;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.databinding.FragmentAddFavoriteBinding;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.environment.PluginEnv;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.service.IMoreService;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.misc.UIComponentParam;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.misc.UIResultCodeConstants;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.misc.UIResultDataUtil;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.protocol.UIResultCallBack;
import com.squareup.picasso.Picasso;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
public class AddFavoriteFragment extends BaseDialogFragment {
private FragmentAddFavoriteBinding binding;
private JSONObject miniAppInfo = null;
private UIComponentParam uiComponentParam = null;
public static AddFavoriteFragment newInstance(UIComponentParam uiComponentParam) {
AddFavoriteFragment fragment = new AddFavoriteFragment();
fragment.uiComponentParam = uiComponentParam;
return fragment;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Dialog dialog = new Dialog(requireContext(), R.style.BottomDialogTheme);
binding = FragmentAddFavoriteBinding.inflate(getLayoutInflater());
View contentView = binding.getRoot();
dialog.setContentView(contentView);
Window window = dialog.getWindow();
if (window != null) {
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
window.setGravity(Gravity.BOTTOM);
window.getAttributes().windowAnimations = R.style.mini_app_bottom_dialog_anim;
}
dialog.setCanceledOnTouchOutside(false);
dialog.setCancelable(false);
return dialog;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
HashMap<String, Object> params = uiComponentParam.getParams();
String miniInfo = (String) params.get("miniAppInfo");
if (!TextUtils.isEmpty(miniInfo)) {
try {
miniAppInfo = new JSONObject(miniInfo);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
binding = FragmentAddFavoriteBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
View parent = (View) view.getParent();
ViewGroup.LayoutParams params = parent.getLayoutParams();
params.height = (int) (getResources().getDisplayMetrics().heightPixels * 0.32);
parent.setLayoutParams(params);
if (miniAppInfo != null) {
String miniAppIcon = miniAppInfo.optString("miniAppIcon");
String miniAppName = miniAppInfo.optString("miniAppName");
setImage(binding.headerIcon, miniAppIcon);
binding.headerTitle.setText(miniAppName + " " + getString(R.string.mini_app_authorize_new_apply));
}
binding.ivAboutUs.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (uiComponentParam != null) {
UIResultCallBack uiResultCallBack = uiComponentParam.getUiResultCallBack();
if (uiResultCallBack != null) {
uiResultCallBack.onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.OPEN_ABOUT));
}
}
}
});
binding.confirmButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
IMoreService moreService = PluginEnv.getInstance().getPluginContext().getContainer().getMoreService();
if (moreService != null) {
if (!moreService.isFavorite(miniAppInfo.optString("miniAppId"))) {
moreService.addToFavorites(miniAppInfo.optString("miniAppId"), getActivity());
}
}
dismiss();
UIResultCallBack uiResultCallBack = uiComponentParam.getUiResultCallBack();
if (uiResultCallBack != null) {
uiResultCallBack.onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.EXIT_MINIAPP));
}
}
});
binding.cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dismiss();
UIResultCallBack uiResultCallBack = uiComponentParam.getUiResultCallBack();
if (uiResultCallBack != null) {
uiResultCallBack.onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.EXIT_MINIAPP));
}
}
});
}
@Override
public void onStart() {
super.onStart();
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
8. Implementar AboutUsFragment
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.alibaba.emas.android.mini.app.common.log.MiniAppLog;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.R;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.databinding.FragmentAboutUsBinding;
import com.alibaba.emas.android.windvane.mini.app.plugin.ui.databinding.FragmentAuthorizeBinding;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.misc.UIComponentParam;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.misc.UIResultCodeConstants;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.misc.UIResultDataUtil;
import com.alibaba.emas.android.windvane.mini.app.pluginsdk.ui.protocol.UIResultCallBack;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
public class AboutUsFragment extends DialogFragment {
private FragmentAboutUsBinding binding;
private JSONObject miniAppInfo = null;
private UIComponentParam uiComponentParam = null;
private UIResultCallBack uiResultCallBack = null;
private static final String TAG = "AboutUsFragment";
public static AboutUsFragment newInstance(UIComponentParam uiComponentParam) {
AboutUsFragment fragment = new AboutUsFragment();
fragment.uiComponentParam = uiComponentParam;
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getData();
}
private void getData() {
if (uiComponentParam != null) {
HashMap<String, Object> params = uiComponentParam.getParams();
uiResultCallBack = uiComponentParam.getUiResultCallBack();
if (params != null) {
String miniAppInfo = (String) params.get("miniAppInfo");
if (!TextUtils.isEmpty(miniAppInfo)) {
try {
this.miniAppInfo = new JSONObject(miniAppInfo);
} catch (JSONException e) {
MiniAppLog.d(TAG, "miniAppInfo is null");
}
}
}
}
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Dialog dialog = new Dialog(requireContext(), R.style.BottomDialogTheme);
binding = FragmentAboutUsBinding.inflate(getLayoutInflater());
View contentView = binding.getRoot();
dialog.setContentView(contentView);
Window window = dialog.getWindow();
if (window != null) {
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
window.setGravity(Gravity.BOTTOM);
window.getAttributes().windowAnimations = R.style.mini_app_bottom_dialog_anim;
}
dialog.setCanceledOnTouchOutside(false);
dialog.setCancelable(false);
return dialog;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
binding = FragmentAboutUsBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (miniAppInfo != null) {
binding.tvNameDes.setText(miniAppInfo.optString("miniAppName"));
binding.tvSloganDes.setText(miniAppInfo.optString("miniAppSlogan"));
binding.tvVersionDes.setText(miniAppInfo.optString("miniAppVersion"));
binding.tvDescription.setText(miniAppInfo.optString("miniAppDescription"));
}
binding.ivBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (uiResultCallBack != null) {
uiResultCallBack.onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.CANCEL));
}
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
3.1.3.2 Implementação para iOS
1. Criar UIPlugin personalizado no cliente
#import "EMASClientCustomUIPlugin.h"
#import <EMASMiniAppService/EMASIUIExtension.h>
#import <EMASMiniAppService/EMASUIComponentParam.h>
#import <EMASMiniAppService/EMASResultCodeConstants.h>
#import "EMASAuthUIViewController.h"
#import "CPErrorPage.h"
#import "CPLoadPage.h"
#import "CPMenuController.h"
#import "CPAboutUsViewController.h"
#import "CPComfirmViewController.h"
#import "CPSettingPage.h"
@interface EMASClientCustomUIPlugin()<EMASIUIExtension>
@end
@implementation EMASClientCustomUIPlugin
- (UIViewController *)createUserDeviceInfoAuthDialogViewController:(EMASUIComponentParam *)uiComponentParam {
NSDictionary *authParams = uiComponentParam.params;
NSDictionary *authDic = authParams[@"authorize"];
//Authorized scopes
NSMutableDictionary *authedScopeDic = [[NSMutableDictionary alloc] init];
//Scopes requiring authorization (filter out unknown scopes)
NSMutableArray * filterArray = [[NSMutableArray alloc] init];
for (NSString *key in authDic.allKeys) {
if ([[authDic objectForKey:key] boolValue]) {
[authedScopeDic setObject:[authDic objectForKey:key] forKey:key];
}else {
[filterArray addObject:key];
}
}
NSDictionary *filterDic = @{@"authorize": filterArray, @"appId": authParams[@"appId"], @"userId": authParams[@"userId"], @"miniAppInfo": authParams[@"miniAppInfo"], @"authed": authedScopeDic};
EMASAuthUIViewController *vc = [[EMASAuthUIViewController alloc] init];
[vc setMiniAppAuth:filterDic];
vc.completionBlock = uiComponentParam.completionBlock;
return vc;
}
- (UIViewController *)createErrorPageViewController:(EMASUIComponentParam *)uiComponentParam {
CPErrorPage* page = [CPErrorPage new];
[page miniAppErrorInfo:uiComponentParam.params];
[page setComplete:^(int index) {
if (index == 0) {
if (uiComponentParam.completionBlock) {
uiComponentParam.completionBlock(@{@"code": EMAS_EXIT_MINIAPP});
}
}else if(index == 1){
if (uiComponentParam.completionBlock) {
uiComponentParam.completionBlock(@{@"code": EMAS_RESTART_MINIAPP});
}
}
}];
return page;
}
- (UIViewController *)createSplashPageViewController:(EMASUIComponentParam *)uiComponentParam {
CPLoadPage* page = [CPLoadPage new];
[page miniAppInfo:uiComponentParam.params];
return page;
}
-(UIViewController*)createMoreDialogViewController:(EMASUIComponentParam *)uiComponentParam{
CPMenuController* moreVC = [CPMenuController new];
[moreVC miniAppInfo:uiComponentParam.params];
moreVC.completionBlock = uiComponentParam.completionBlock;
return moreVC;
}
- (UIViewController *)createAboutUsDialogViewController:(EMASUIComponentParam *)uiComponentParam {
CPAboutUsViewController *aboutUsVC = [[CPAboutUsViewController alloc] init];
[aboutUsVC miniAppInfo:uiComponentParam.params];
aboutUsVC.completionBlock = uiComponentParam.completionBlock;
return aboutUsVC;
}
- (UIViewController *)createFavoriteDialogViewController:(EMASUIComponentParam *)uiComponentParam {
CPComfirmViewController *favoriteVC = [[CPComfirmViewController alloc] init];
CPCfmViewConfig *config = [[CPCfmViewConfig alloc] init];
config.miniAppInfo = uiComponentParam.params;
favoriteVC.completionBlock = uiComponentParam.completionBlock;
favoriteVC.config = config;
return favoriteVC;
}
- (UIViewController *)createSettingsDialogViewController:(EMASUIComponentParam *)uiComponentParam {
CPSettingPage* settingsVC = [CPSettingPage new];
[settingsVC miniAppInfo:uiComponentParam.params];
settingsVC.completionBlock = uiComponentParam.completionBlock;
return settingsVC;
}
@end
2. Implementar UserDeviceInfoAuthDialogViewController
#import "EMASAuthUIViewController.h"
#import "EMASAuthUITopInfoView.h"
#import "UIColor+SAHexString.h"
#import "EMASDefaultAuthTabCell.h"
#import "EMASAuthInfoTabCell.h"
#import "EMASAuthorizeInfoModel.h"
#import "UIColor+SAHexString.h"
#import "EMASUIUtils.h"
#import <EMASMiniAppService/EMASPluginEnv.h>
#import <EMASMiniAppService/EMASIAuthService.h>
#import <EMASMiniAppService/EMASUIComponentParam.h>
#import <EMASMiniAppService/EMASResultCodeConstants.h>
static NSString * const defaultIdentifier = @"defalultIdentifier";
static NSString * const authIdentifier = @"authIdentifier";
@interface EMASAuthUIViewController ()<UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) UIView *containerView;
@property (nonatomic, strong) EMASAuthUITopInfoView *topView;
@property (nonatomic, strong) UILabel *authTipLabel;
@property (nonatomic, strong) UIButton *cancelBtn;
@property (nonatomic, strong) UIButton *sureBtn;
@property (nonatomic, strong) UITableView *authTabView;
@property (nonatomic, strong) NSMutableArray *filterScopes;
@property (nonatomic, strong) NSMutableArray *scopeAuthArray;
@property (nonatomic, assign) BOOL isMultiple;
@property (nonatomic, strong) NSDictionary *scopeDic;
@property (nonatomic, strong)NSDictionary *scopeTipDic;
@property (nonatomic, strong) NSDictionary *permissionDic;
//Miniapp Information
@property (nonatomic, strong) NSDictionary *miniAppInfo;
//Miniapp App ID
@property (nonatomic, strong) NSString *appId;
//User ID
@property (nonatomic, strong)NSString *userId;
//Update TableView height layout
@property (nonatomic, strong)NSLayoutConstraint *authTabHeightConstraint;
@property (nonatomic, strong)NSLayoutConstraint *cancelBottomConstraint;
@property (nonatomic, strong) NSDictionary *unknownAuthDic;
@property (nonatomic, strong) NSDictionary *authedDic;
@end
@implementation EMASAuthUIViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.5];
[self initDefaultAuthInfo];
[self initSubViewUI];
self.containerView.layer.cornerRadius = 12.0;
self.containerView.layer.masksToBounds = YES;
[self.topView setMiniAppInfo:self.miniAppInfo];
[self updateSubViewLayout];
[self getScopeAuthInfo];
[self updateAuthTipInfo];
[self.authTabView reloadData];
}
- (void)initSubViewUI {
self.containerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.containerView];
self.topView.translatesAutoresizingMaskIntoConstraints = NO;
[self.containerView addSubview:self.topView];
self.authTipLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.containerView addSubview:self.authTipLabel];
self.authTabView.translatesAutoresizingMaskIntoConstraints = NO;
[self.containerView addSubview:self.authTabView];
self.authTabView.delegate = self;
self.authTabView.dataSource = self;
self.authTabView.rowHeight = UITableViewAutomaticDimension;
self.authTabView.estimatedRowHeight = 70;
self.authTabView.estimatedSectionHeaderHeight = 0;
self.authTabView.estimatedSectionFooterHeight = 0;
if (@available(IOS 11.0, *)) {
self.authTabView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
self.authTabView.scrollEnabled = NO;
self.cancelBtn.translatesAutoresizingMaskIntoConstraints = NO;
[self.containerView addSubview:self.cancelBtn];
self.sureBtn.translatesAutoresizingMaskIntoConstraints = NO;
[self.containerView addSubview:self.sureBtn];
[self.cancelBtn addTarget:self action:@selector(clickToCancelAuth:) forControlEvents:UIControlEventTouchUpInside];
[self.sureBtn addTarget:self action:@selector(clickToAllowAuth:) forControlEvents:UIControlEventTouchUpInside];
[self addSubViewLayout];
[self setDefaultStyle];
}
- (void)clickToCancelAuth:(UIButton *)btn {
if (self.completionBlock) {
NSDictionary *dic = @{
@"code": EMAS_CANCEL,
@"msg": @"User cancels authorization"
};
self.completionBlock(dic);
}
}
- (void)clickToAllowAuth:(UIButton *)btn {
NSMutableArray *array = [[NSMutableArray alloc] init];
for (EMASAuthorizeInfoModel *model in self.scopeAuthArray) {
if (model.isSelect) {
[array addObject:model.scope];
}
}
//No values currently require authorization
if (array.count == 0) {
//Text information and display -- logic handling
return;
}
id<EMASIAuthService> authService = EMASPluginEnv.shareInstance.getContainerContext.getAuthService;
if (authService && [authService respondsToSelector:@selector(saveAuthList:userId:miniAppId:)]) {
BOOL isSuccess = [authService saveAuthList:array userId:self.userId miniAppId:self.appId];
NSMutableDictionary *scopDic = [[NSMutableDictionary alloc] init];
for (EMASAuthorizeInfoModel *model in self.scopeAuthArray) {
BOOL isAuth = isSuccess ? model.isSelect : isSuccess;
[scopDic setObject:@(isAuth) forKey:model.scope];
}
if (self.authedDic && self.authedDic.allKeys.count != 0) {
[scopDic addEntriesFromDictionary:self.authedDic];
}
if (self.unknownAuthDic && self.unknownAuthDic.allKeys.count != 0) {
[scopDic addEntriesFromDictionary:self.unknownAuthDic];
}
if (self.completionBlock) {
NSDictionary *dic = @{
@"code": EMAS_CONFIRM,
@"msg": @"",
@"data": scopDic
};
self.completionBlock(dic);
}
}
}
- (void)addSubViewLayout {
// Default setting table height to 60
self.authTabHeightConstraint = [self.authTabView.heightAnchor constraintEqualToConstant:60];
[NSLayoutConstraint activateConstraints:@[
[self.containerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:0],
[self.containerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:0],
[self.containerView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:0],
[self.topView.leadingAnchor constraintEqualToAnchor:self.containerView.leadingAnchor constant:24],
[self.topView.trailingAnchor constraintEqualToAnchor:self.containerView.trailingAnchor constant:-24],
[self.topView.topAnchor constraintEqualToAnchor:self.containerView.topAnchor constant:0],
[self.topView.heightAnchor constraintEqualToConstant:56],
[self.authTipLabel.leadingAnchor constraintEqualToAnchor:self.containerView.leadingAnchor constant:24],
[self.authTipLabel.trailingAnchor constraintEqualToAnchor:self.containerView.trailingAnchor constant:-24],
[self.authTipLabel.topAnchor constraintEqualToAnchor:self.topView.bottomAnchor constant:16],
[self.authTipLabel.heightAnchor constraintEqualToConstant:22.0],
[self.authTabView.leadingAnchor constraintEqualToAnchor:self.containerView.leadingAnchor constant:0],
[self.authTabView.trailingAnchor constraintEqualToAnchor:self.containerView.trailingAnchor constant:0],
[self.authTabView.topAnchor constraintEqualToAnchor:self.authTipLabel.bottomAnchor constant:0],
self.authTabHeightConstraint,
[self.cancelBtn.topAnchor constraintEqualToAnchor:self.authTabView.bottomAnchor constant:20],
[self.cancelBtn.leadingAnchor constraintEqualToAnchor:self.containerView.leadingAnchor constant:57.5],
[self.cancelBtn.widthAnchor constraintEqualToAnchor:self.sureBtn.widthAnchor],
[self.cancelBtn.heightAnchor constraintEqualToConstant:40],
[self.cancelBtn.bottomAnchor constraintEqualToAnchor:self.containerView.bottomAnchor constant:-54],
[self.sureBtn.trailingAnchor constraintEqualToAnchor:self.containerView.trailingAnchor constant:-57.5],
[self.sureBtn.heightAnchor constraintEqualToAnchor:self.cancelBtn.heightAnchor],
[self.sureBtn.leadingAnchor constraintEqualToAnchor:self.cancelBtn.trailingAnchor constant:20],
[self.sureBtn.centerYAnchor constraintEqualToAnchor:self.cancelBtn.centerYAnchor constant:0]
]];
}
- (void)updateSubViewLayout {
CGFloat h = 56 + 16 + 22 + 20 + 43 + 54;
CGFloat max_tab_h = self.view.frame.size.height/5.0 * 4 - h;
if (self.isMultiple) {
CGFloat tab_h = 93 * self.filterScopes.count;
self.authTabHeightConstraint.constant = tab_h < max_tab_h ? tab_h : max_tab_h;
if (tab_h > max_tab_h) {
self.authTabView.scrollEnabled = YES;
}
[UIView animateWithDuration:0.3 animations:^{
[self.containerView layoutIfNeeded];
}];
}
}
- (void)updateAuthTipInfo {
NSString *multipleTopInfo = [EMASUIUtils localizedStringForKey:@"miniapp_auth_tip" value:@"Access your following information"];
NSString *AuthTipString = self.isMultiple ? multipleTopInfo : self.scopeTipDic[self.filterScopes.firstObject];
self.authTipLabel.text = AuthTipString;
}
- (void)initDefaultAuthInfo {
self.scopeTipDic = @{
@"location": @"Access your location information",
@"contacts": @"Access your contact list information",
@"bluetooth": @"Access your bluetooth information",
@"camera": @"Access your camera information",
@"microphone": @"Access your microphone information",
@"album":@"Access your photo album information",
@"file": @"Access your file information",
@"call": @"Access your call information",
@"vibrate": @"Access your device vibration information",
@"screen":@"Access your screen information"
};
self.permissionDic = @{
@"location": @"Location",
@"contacts": @"Contacts",
@"bluetooth": @"Bluetooth",
@"camera":@"Camera",
@"microphone": @"Microphone",
@"album":@"Album",
@"file": "Files",
@"call": @"Calls",
@"vibrate": @"Vibration",
@"screen":@"Screen"
};
self.scopeDic = @{
@"location": @"This miniapp will access your location to provide address recommendations and localized services",
@"contacts": @"This miniapp will access your contacts to quickly find contacts or invite friends",
@"bluetooth": @"This miniapp will access bluetooth to connect to smart devices and enable data interaction",
@"camera": @"This miniapp will access your camera for taking photos or scanning QR codes",
@"microphone": @"This miniapp will access your microphone for voice input and voice-based features",
@"album": @"This miniapp will access your photo album to select and upload images for sharing or editing",
@"file": @"This miniapp will access files to read and manage documents or resource files required by the application",
@"call": @"This miniapp will access call logs to identify incoming calls or enhance communication-related features",
@"vibrate": @"This miniapp will access device vibration controls to provide tactile feedback and enhance operational experience",
@"screen": @"This miniapp will access screen information to adapt interface display, support screenshot and screen recording features, and improve user interaction"
};
}
- (void)setDefaultStyle {
self.cancelBtn.backgroundColor = [UIColor colorWithHexString:@"#F1F2F3" alpha:1.0];
self.sureBtn.backgroundColor = [UIColor colorWithHexString:@"#2561F5" alpha:1.0];
self.cancelBtn.layer.cornerRadius = 4.0;
self.cancelBtn.layer.masksToBounds = YES;
self.sureBtn.layer.cornerRadius = 4.0;
self.sureBtn.layer.masksToBounds = YES;
self.cancelBtn.titleLabel.font = [UIFont systemFontOfSize:17.0];
self.sureBtn.titleLabel.font = [UIFont systemFontOfSize:17.0];
[self.cancelBtn setTitleColor:[UIColor colorWithHexString:@"#2561F5" alpha:1.0] forState:UIControlStateNormal];
[self.sureBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
NSString *cancelTitle = @"Reject";
[self.cancelBtn setTitle:cancelTitle forState:UIControlStateNormal];
NSString *sureTitle = @"Allow";
[self.sureBtn setTitle:sureTitle forState:UIControlStateNormal];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.scopeAuthArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (_isMultiple) {
EMASAuthInfoTabCell *cell = [tableView dequeueReusableCellWithIdentifier:authIdentifier];
if (!cell) {
cell = [[EMASAuthInfoTabCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:authIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.authModel = self.scopeAuthArray[indexPath.row];
return cell;
}
EMASDefaultAuthTabCell *cell = [tableView dequeueReusableCellWithIdentifier:defaultIdentifier];
if (!cell) {
cell = [[EMASDefaultAuthTabCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:defaultIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
EMASAuthorizeInfoModel *model = self.scopeAuthArray[indexPath.row];
cell.authInfo = model.authInfo;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:true];
if (self.isMultiple) {
EMASAuthorizeInfoModel *model = self.scopeAuthArray[indexPath.row];
model.isSelect = !model.isSelect;
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self updateAuthButtonState];
}
}
- (void)updateAuthButtonState {
BOOL isContainerAuth = NO;
for (EMASAuthorizeInfoModel *model in self.scopeAuthArray) {
if (model.isSelect) {
isContainerAuth = YES;
break;
}
}
if (isContainerAuth) {
self.sureBtn.alpha = 1.0;
self.sureBtn.userInteractionEnabled = YES;
}else {
self.sureBtn.alpha = 0.3;
self.sureBtn.userInteractionEnabled = NO;
}
}
- (void)setMiniAppAuth:(NSDictionary *)params {
NSArray *scopes = params[@"authorize"];
self.miniAppInfo = params[@"miniAppInfo"];
self.appId = params[@"appId"];
self.userId = params[@"userId"];
self.unknownAuthDic = params[@"unknownAuth"];
self.authedDic = params[@"authed"];
[self.filterScopes removeAllObjects];
for (NSString *scope in scopes) {
[self.filterScopes addObject:scope];
}
_isMultiple = self.filterScopes.count != 1;
}
- (void)getScopeAuthInfo {
[self.scopeAuthArray removeAllObjects];
for (NSString *scope in self.filterScopes) {
EMASAuthorizeInfoModel *model = [[EMASAuthorizeInfoModel alloc] init];
model.isSelect = YES;
model.captialScope = self.permissionDic[scope];
model.authInfo = self.scopeDic[scope];
model.scopeTips = self.scopeTipDic[scope];
model.scope = scope;
[self.scopeAuthArray addObject:model];
}
}
- (NSMutableArray *)filterScopes {
if (!_filterScopes) {
_filterScopes = [[NSMutableArray alloc] init];
}
return _filterScopes;
}
- (NSMutableArray *)scopeAuthArray {
if (!_scopeAuthArray) {
_scopeAuthArray = [[NSMutableArray alloc] init];
}
return _scopeAuthArray;
}
- (UIView *)containerView {
if (!_containerView) {
_containerView = [[UIView alloc] init];
_containerView.backgroundColor = [UIColor whiteColor];
}
return _containerView;
}
- (EMASAuthUITopInfoView *)topView {
if (!_topView) {
_topView = [[EMASAuthUITopInfoView alloc] init];
}
return _topView;
}
- (UILabel *)authTipLabel {
if (!_authTipLabel) {
_authTipLabel = [[UILabel alloc] init];
_authTipLabel.font = [UIFont systemFontOfSize:17.0];
_authTipLabel.textColor = [UIColor colorWithHexString:@"#1F2024" alpha:1.0];
}
return _authTipLabel;
}
- (UITableView *)authTabView {
if (!_authTabView) {
_authTabView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
_authTabView.backgroundColor = [UIColor whiteColor];
_authTabView.separatorStyle = UITableViewCellSeparatorStyleNone;
}
return _authTabView;
}
- (UIButton *)cancelBtn {
if (!_cancelBtn) {
_cancelBtn = [UIButton buttonWithType:UIButtonTypeCustom];
}
return _cancelBtn;
}
- (UIButton *)sureBtn {
if (!_sureBtn) {
_sureBtn = [UIButton buttonWithType:UIButtonTypeCustom];
}
return _sureBtn;
}
@end
3. Implementar ErrorPageViewController
#import "CPErrorPage.h"
#import "CPCommonTool.h"
#import <EMASMiniAppApi/EMASMiniAppConstants.h>
@interface CPErrorPage ()
@property (nonatomic,strong) UILabel *titleLabel;
@property (nonatomic,strong) UILabel *errorLabel;
@property (nonatomic,strong) UIButton *refreshBtn;
@property (nonatomic,strong) UIButton *quitBtn;
@property (nonatomic,strong) UIImageView *mainImg;
@end
@implementation CPErrorPage
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = UIColor.whiteColor;
[self setupErrorView];
[self loadData];
}
-(void)loadData{
if (self.config) {
CPErrorViewConfig* cnf = self.config;
if(kStrExist(cnf.mainIconStr)){
self.mainImg.image =[CPCommonTool getImageByName:cnf.mainIconStr];
}
if(kStrExist(cnf.errorTxt)){
[self.titleLabel setText:cnf.errorTxt];
}
if(kStrExist(cnf.errorCodeTxt)){
[self.errorLabel setText:cnf.errorCodeTxt];
}
if(kStrExist(cnf.refreshTxt)){
[self.refreshBtn setTitle:cnf.refreshTxt forState:(UIControlStateNormal)];
}
if(kStrExist(cnf.quitTxt)){
[self.quitBtn setTitle:cnf.quitTxt forState:(UIControlStateNormal)];
}
self.quitBtn.hidden = !cnf.showQuitBtn;
self.refreshBtn.hidden = !cnf.showRefreshBtn;
}else{
CPErrorViewConfig* cnf = [CPErrorViewConfig new];
self.config = cnf;
}
//Adjust constraints based on conditions
[self updateConstraints];
}
-(void)setupErrorView{
// 1. Create container view
UIView* container = [UIView new];
container.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:container];
// 2. Create error image and add to the container
UIImageView *errorImage = [[UIImageView alloc] initWithImage:[CPCommonTool getImageByName:@"app_load_failure"]];
errorImage.contentMode = UIViewContentModeScaleAspectFit;
errorImage.translatesAutoresizingMaskIntoConstraints = NO;
self.mainImg = errorImage;
[container addSubview:errorImage]; // Add to the container
// 3. Create error prompt label and add to the container
UILabel *errorLabel = self.titleLabel;
errorLabel.text = kLocalString(@"app_failed_to_load");
errorLabel.textAlignment = NSTextAlignmentCenter;
errorLabel.textColor = kColorWithHex(@"#474A52");
errorLabel.font = kFont(16);
errorLabel.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:errorLabel]; // Add to the container
// Error code label constraints
UILabel *codeLabel = self.errorLabel;
codeLabel.text = [NSString stringWithFormat:@"%@:0000",kLocalString(@"error_code")];
codeLabel.textAlignment = NSTextAlignmentCenter;
codeLabel.textColor = kColorWithHex(@"#81848F");
codeLabel.font = kFont(13);
codeLabel.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:codeLabel];
// Create refresh button
UIButton *refreshButton = self.refreshBtn;
[refreshButton setTitle:kLocalString(@"miniapp_error_refresh_btn_title") forState:UIControlStateNormal];
[refreshButton addTarget:self action:@selector(refreshAction) forControlEvents:UIControlEventTouchUpInside];
[container addSubview:refreshButton];
// Create exit button
UIButton *exitButton = self.quitBtn;
[exitButton setTitle:kLocalString(@"miniapp_error_exit_btn_title") forState:UIControlStateNormal];
[exitButton addTarget:self action:@selector(exitAction) forControlEvents:UIControlEventTouchUpInside];
[container addSubview:exitButton];
// 4. Set constraints
[NSLayoutConstraint activateConstraints:@[
// Container constraints
[container.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[container.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor constant:-hPix(100)],
[container.widthAnchor constraintEqualToAnchor:self.view.widthAnchor multiplier:0.8],
// Error image constraints
[errorImage.topAnchor constraintEqualToAnchor:container.topAnchor],
[errorImage.centerXAnchor constraintEqualToAnchor:container.centerXAnchor],
[errorImage.widthAnchor constraintEqualToConstant:wPix(120)],
[errorImage.heightAnchor constraintEqualToConstant:wPix(120)],
// Error label constraints
[errorLabel.topAnchor constraintEqualToAnchor:errorImage.bottomAnchor constant:hPix(16)],
[errorLabel.leadingAnchor constraintEqualToAnchor:container.leadingAnchor],
[errorLabel.trailingAnchor constraintEqualToAnchor:container.trailingAnchor],
// Error code label constraints
[codeLabel.topAnchor constraintEqualToAnchor:errorLabel.bottomAnchor constant:hPix(8)],
[codeLabel.centerXAnchor constraintEqualToAnchor:container.centerXAnchor],
]];
}
-(void)updateConstraints{
CPErrorViewConfig* cnf = self.config;
UIButton* refreshButton = self.refreshBtn;
UIButton* exitButton = self.quitBtn;
UILabel* codeLabel = self.errorLabel;
UIView* container = self.refreshBtn.superview;
NSMutableArray* arr = @[].mutableCopy;
CGFloat btn_wid = wPix(120);
CGFloat btn_hei = hPix(40);
if (cnf.showRefreshBtn) {//showQuitBtn
if (cnf.showQuitBtn) {//showRefreshBtn
[arr addObjectsFromArray:@[
[exitButton.topAnchor constraintEqualToAnchor:codeLabel.bottomAnchor constant:hPix(40)],
[exitButton.trailingAnchor constraintEqualToAnchor:container.centerXAnchor constant:-10],
[exitButton.widthAnchor constraintEqualToConstant:btn_wid],
[exitButton.heightAnchor constraintEqualToConstant:btn_hei],
[refreshButton.leadingAnchor constraintEqualToAnchor:container.centerXAnchor constant:10],
[container.bottomAnchor constraintEqualToAnchor:exitButton.bottomAnchor constant:0]
]];
}else{
[arr addObjectsFromArray:@[
[refreshButton.centerXAnchor constraintEqualToAnchor:container.centerXAnchor constant:0],
[container.bottomAnchor constraintEqualToAnchor:refreshButton.bottomAnchor constant:0]
]];
}
[arr addObjectsFromArray:@[
[refreshButton.topAnchor constraintEqualToAnchor:codeLabel.bottomAnchor constant:hPix(40)],
[refreshButton.widthAnchor constraintEqualToConstant:btn_wid],
[refreshButton.heightAnchor constraintEqualToConstant:btn_hei],
]];
}else{
if (cnf.showQuitBtn) {
[arr addObjectsFromArray:@[
[exitButton.topAnchor constraintEqualToAnchor:codeLabel.bottomAnchor constant:hPix(40)],
[exitButton.centerXAnchor constraintEqualToAnchor:container.centerXAnchor constant:0],
[exitButton.widthAnchor constraintEqualToConstant:btn_wid],
[exitButton.heightAnchor constraintEqualToConstant:btn_hei],
[container.bottomAnchor constraintEqualToAnchor:exitButton.bottomAnchor constant:0]
]];
}else{
[arr addObject: [container.bottomAnchor constraintEqualToAnchor:codeLabel.bottomAnchor constant:0]];
}
}
[NSLayoutConstraint activateConstraints:arr];
}
- (void)refreshAction {
// Refresh logic implementation
if (self.complete) {
self.complete(1);
}
}
- (void)exitAction {
// Exit logic implementation
if (self.complete) {
self.complete(0);
}
}
#pragma mark -
- (void)miniAppErrorInfo:(NSDictionary *)params{
CPErrorViewConfig* cfn = [CPErrorViewConfig new];
if ([params objectForKey:@"ErrorCode"] == nil) {
self.config = cfn;
return;
}
int code = [params[@"ErrorCode"] intValue];
switch (code) {
case EMAS_ERROR_CODE_NETWORK_ERROR://Network error
case EMAS_ERROR_CODE_NETWORK_RESPONSE_EMPTY://Network error
case EMAS_ERROR_CODE_NETWORK_RESPONSE_SUCCESS_FALSE://Network error
case EMAS_ERROR_CODE_URL_VERSION_EMPTY://Network error
{
[cfn setErrorType:(CPErrorTypeConnectionError) errorCode:code];
}
break;
case EMAS_ERROR_CODE_NETWORK_RESPONSE_FORMAT_ERROR://Unable to open this page
{
[cfn setErrorType:(CPErrorTypeOpenError) errorCode:code];
}
break;
case EMAS_ERROR_CODE_MINI_APP_DOWNLOAD_ERROR://Miniapp failed to load
case EMAS_ERROR_CODE_MINI_APP_UNZIP_ERROR://Miniapp failed to load
case EMAS_ERROR_CODE_MINI_APP_CACHE_ERROR://Miniapp failed to load
case EMAS_ERROR_CODE_MINI_APP_TYPE_NOT_SUPPORT://Failed to load
case EMAS_ERROR_CODE_WINDVANE_NOT_USED://Failed to load
case EMAS_ERROR_CODE_UNIAPP_NOT_USED://Miniapp failed to load
{
[cfn setErrorType:(CPErrorTypeLoadError) errorCode:code];
}
break;
case EMAS_ERROR_CODE_MINI_APP_TAKEN_OFF://Miniapp does not exist
{
[cfn setErrorType:(CPErrorTypeOfflineError) errorCode:code];
}
break;
default:
break;
}
self.config = cfn;
}
#pragma mark - Setter&Getter
-(UILabel*)titleLabel{
if (!_titleLabel) {
_titleLabel = [UILabel new];
}
return _titleLabel;
}
-(UILabel*)errorLabel{
if (!_errorLabel) {
_errorLabel = [UILabel new];
}
return _errorLabel;
}
-(UIButton*)refreshBtn{
if (!_refreshBtn) {
_refreshBtn = [CPCommonTool getThemeDarkButton];
}
return _refreshBtn;
}
-(UIButton*)quitBtn{
if (!_quitBtn) {
_quitBtn = [CPCommonTool getThemeLightButton];
}
return _quitBtn;
}
@end
4. Implementar SplashPageViewController:
Para obter o progresso de carregamento e as informações do miniapp, implemente o protocolo EMASISplashExtension.
#import "CPLoadPage.h"
#import "CPCommonTool.h"
@interface CPLoadPage ()
@property(nonatomic,strong)UIProgressView* progressView;
@property(nonatomic,strong)UILabel*percentLabel;
@property (nonatomic,strong)UILabel *titleLabel;
@property (nonatomic,strong)UIImageView *appIcon;
@end
@implementation CPLoadPage
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setupViews];
[self loadData];
}
-(void)loadData{
if (self.config) {
CPLoadViewConfig* cnf = self.config;
self.progressView.hidden = self.percentLabel.hidden = !cnf.showProgress;
if (cnf.iconUrl) {
[self.appIcon loadUrl:cnf.iconUrl];
}
if(kStrExist(cnf.appName)){
[self.titleLabel setText:cnf.appName];
}
if (cnf.titleColor) {
[self.titleLabel setTextColor:cnf.titleColor];
}
if (cnf.progressColor) {
[self.progressView setTintColor:cnf.progressColor];
}
}
}
-(void)setupViews{
self.view.backgroundColor = [UIColor whiteColor];
// Create container view
UIView *loadContainer = [UIView new];
loadContainer.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:loadContainer];
// Add image
UIImageView *imageView = [[UIImageView alloc] initWithImage:[CPCommonTool getImageByName:(@"miniapp_default_icon")]];
self.appIcon = imageView;
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.translatesAutoresizingMaskIntoConstraints = NO;
[loadContainer addSubview:imageView];
// Add application name label
UILabel *label = self.titleLabel;
label.text = @"Miniapp Demo";
label.font = kFontWithWeight(16,500);
label.textAlignment = NSTextAlignmentCenter;
label.translatesAutoresizingMaskIntoConstraints = NO;
[loadContainer addSubview:label];
// Add progress bar
self.progressView.progress = 0;
self.progressView.translatesAutoresizingMaskIntoConstraints = NO;
self.progressView.tintColor = kColorWithHex(CP_THEME_COLOR);
[loadContainer addSubview:self.progressView];
// Add percentage label
self.percentLabel.text = @"0%";
self.percentLabel.textColor = [UIColor grayColor];
self.percentLabel.font = kFontWithWeight(12,400);
self.percentLabel.translatesAutoresizingMaskIntoConstraints = NO;
[loadContainer addSubview:self.percentLabel];
NSArray* arr = @[
// Container constraints
[loadContainer.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[loadContainer.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor constant:-64],
[loadContainer.widthAnchor constraintEqualToAnchor:self.view.widthAnchor multiplier:1],
// Image constraints
[imageView.topAnchor constraintEqualToAnchor:loadContainer.topAnchor],
[imageView.centerXAnchor constraintEqualToAnchor:loadContainer.centerXAnchor],
[imageView.widthAnchor constraintEqualToConstant:wPix(56)],
[imageView.heightAnchor constraintEqualToConstant:wPix(56)],
// Application name label constraints
[label.topAnchor constraintEqualToAnchor:imageView.bottomAnchor constant:hPix(20)],
[label.leadingAnchor constraintEqualToAnchor:loadContainer.leadingAnchor],
[label.trailingAnchor constraintEqualToAnchor:loadContainer.trailingAnchor],
// Progress bar constraints
[self.progressView.topAnchor constraintEqualToAnchor:label.bottomAnchor constant:hPix(22)],
[self.progressView.centerXAnchor constraintEqualToAnchor:loadContainer.centerXAnchor],
[self.progressView.widthAnchor constraintEqualToConstant:wPix(160)],
// Percentage label constraints
[self.percentLabel.topAnchor constraintEqualToAnchor:self.progressView.bottomAnchor constant:hPix(8)],
[self.percentLabel.centerXAnchor constraintEqualToAnchor:self.progressView.centerXAnchor],
// Container bottom constraint
[loadContainer.bottomAnchor constraintEqualToAnchor:self.progressView.bottomAnchor constant:0]
];
// Set constraints
[NSLayoutConstraint activateConstraints:arr];
}
-(void)updateProgress:(CGFloat)progress{
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressView setProgress:progress animated:YES];
self.percentLabel.text = [NSString stringWithFormat:@"%0.0f%%",progress*100];
});
}
#pragma mark - Setter&Getter
-(void)setConfig:(CPLoadViewConfig *)config{
_config = config;
}
-(UIProgressView*)progressView{
if (!_progressView) {
_progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
}
return _progressView;
}
-(UILabel*)titleLabel{
if (!_titleLabel) {
_titleLabel = [UILabel new];
}
return _titleLabel;
}
-(UILabel*)percentLabel{
if (!_percentLabel) {
_percentLabel = [UILabel new];
}
return _percentLabel;
}
#pragma mark -
- (void)setMiniAppDownloadProgress:(CGFloat)progress{
[self updateProgress:progress];
}
- (void)miniAppInfo:(NSDictionary *)params{
dispatch_async(dispatch_get_main_queue(), ^{
CPLoadViewConfig* cnf = [CPLoadViewConfig new];
self.config = cnf;
self.config.appName = params[@"miniAppName"];
self.config.iconUrl = params[@"miniAppIcon"];
[self loadData];
});
}
@end
5. Implementar MoreViewController
#import "CPMenuController.h"
#import "CPCommonTool.h"
#import "CPSettingPage.h"
#import "CPAboutUsViewController.h"
#import "CPComfirmViewController.h"
#import <EMASMiniAppService/EMASPluginEnv.h>
#import <EMASMiniAppService/EMASResultCodeConstants.h>
#import <EMASServiceManager/EMASServiceManager.h>
#import <EMASMiniAppApi/EMASMiniAppFavoriteService.h>
#import <EMASMiniAppApi/EMASMiniAppMoreService.h>
#define kPopupHeight hPix(355.0)
#define kCornerRadius 10.0
#define kIconSize 24.0
#define kItemSpacing 12.0
#define kItemWid wPix(72)
#define kItemHei hPix(94)
#define kCollectionHeight hPix(185.0)
#define kCancelButtonHeight hPix(56.0)
#define kCancelButtonBottomMargin hPix(34.0)
#define CPMENU_CELL_ICON_TAG 1122
#define CPMENU_CELL_ICON_CONTAINER_TAG 1234
#define CPMENU_CELL_TITLE_TAG 1123
typedef NS_ENUM(NSInteger, MenuItemKey) {
MenuItemKeyAddToFav = 0,
MenuItemKeySettings,
MenuItemKeyCopyLink,
MenuItemKeyShare,
MenuItemKeyRefresh,
MenuItemKeyAboutUs
};
@interface CPMenuItem : NSObject
@property (nonatomic,copy) NSString *title;
@property (nonatomic,assign) MenuItemKey key;
@property (nonatomic,copy) NSString *imgName;
@property (nonatomic,assign)NSInteger status;
@property (nonatomic,copy) NSString *vice_title;
@property (nonatomic,copy) NSString *vice_imgName;
@property (nonatomic, assign) BOOL isAvailable;
@end
@implementation CPMenuItem
- (instancetype)init
{
self = [super init];
if (self) {
self.status = 1;
}
return self;
}
@end
@interface CPMenuController () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (nonatomic, strong) UIView *containerView;
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) NSArray<CPMenuItem*> *items;
@property (nonatomic, strong) UIButton *cancelButton;
@property (nonatomic,strong) UIImageView *appIcon;
@property (nonatomic, strong)NSDictionary *miniAppInfo;
@end
@implementation CPMenuController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupData];
[self setupUI];
[self setupConstraints];
}
- (void)miniAppInfo:(NSDictionary *)info {
self.miniAppInfo = info;
}
-(void)setupData{
NSString *miniAppId = self.miniAppInfo[@"miniAppId"] ?: @"";
BOOL favoriteAvailable = NO;
BOOL isFavorite = NO;
BOOL copyLinkAvailable = NO;
BOOL shareAvailable = NO;
BOOL settingsAvailable = NO;
BOOL isCustomWindow = NO;
BOOL refreshMiniAppAvailable = YES;
id<EMASMiniAppFavoriteService> favService = [[EMASServiceManager sharedInstance] serviceForProtocol:@"EMASMiniAppFavoriteService"];
if (favService) {
favoriteAvailable = YES;
if ([favService respondsToSelector:@selector(isFavorite:)]) {
isFavorite = [favService isFavorite:miniAppId];
}
}
NSString *miniAppUrl = self.miniAppInfo[@"miniAppUrl"] ?: @"";
if (![miniAppUrl isEqualToString:@""]) {
copyLinkAvailable = YES;
settingsAvailable = YES;
}
id<EMASMiniAppMoreService> moreService = [[EMASServiceManager sharedInstance] serviceForProtocol:@"EMASMiniAppMoreService"];
if (moreService && [moreService respondsToSelector:@selector(share:)]) {
shareAvailable = YES;
}
NSMutableArray *mutableMenuItems = [[NSMutableArray alloc] init];
if (favoriteAvailable) {
if (isFavorite) {
[mutableMenuItems addObject:@{@"title": kLocalString(@"miniapp_more_remove_favorite_info"), @"image": @"miniapp_more_remove_favorite", @"key": @(MenuItemKeyAddToFav), @"isAvailabel":@(favoriteAvailable)}];
}else {
[mutableMenuItems addObject:@{@"title": kLocalString(@"miniapp_more_add_favorite_info"), @"image": @"miniapp_more_add_favorite", @"key": @(MenuItemKeyAddToFav), @"isAvailabel":@(favoriteAvailable)}];
}
}
if (settingsAvailable) {
[mutableMenuItems addObject:@{@"title": kLocalString(@"miniapp_more_settings_info"), @"image": @"miniapp_more_settings", @"key": @(MenuItemKeySettings), @"isAvailabel":@(settingsAvailable)}];
}
if (copyLinkAvailable) {
[mutableMenuItems addObject:@{@"title": kLocalString(@"miniapp_more_copy_link_info"), @"image": @"miniapp_more_copy_link", @"key": @(MenuItemKeyCopyLink),@"isAvailabel":@(copyLinkAvailable)}];
}
if (shareAvailable) {
[mutableMenuItems addObject:@{@"title": kLocalString(@"miniapp_more_share_url_info"), @"image": @"miniapp_more_share_url", @"key": @(MenuItemKeyShare), @"isAvailabel":@(shareAvailable)}];
}
if (refreshMiniAppAvailable) {
[mutableMenuItems addObject:@{@"title": kLocalString(@"miniapp_more_refresh_info"), @"image": @"miniapp_more_refresh", @"key": @(MenuItemKeyRefresh), @"isAvailabel":@(refreshMiniAppAvailable)}];
}
[mutableMenuItems addObject:@{@"title": kLocalString(@"miniapp_more_about_us_info"), @"image": @"miniapp_more_about_us", @"key": @(MenuItemKeyAboutUs), @"isAvailabel":@(YES)}];
NSMutableArray *itemsArr = [[NSMutableArray alloc] init];
for (int i = 0; i < mutableMenuItems.count; i++) {
CPMenuItem* item = [CPMenuItem new];
item.title = mutableMenuItems[i][@"title"];
item.imgName = mutableMenuItems[i][@"image"];
item.key = [mutableMenuItems[i][@"key"] intValue];
item.isAvailable = [mutableMenuItems[i][@"isAvailabel"] boolValue];
[itemsArr addObject:item];
}
self.items = itemsArr;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[UIView animateWithDuration:0.3 animations:^{
self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
self.containerView.transform = CGAffineTransformIdentity;
}];
}
- (void)setupUI {
self.view.backgroundColor = [UIColor clearColor];
UIView* topView = [UIView new];
topView.translatesAutoresizingMaskIntoConstraints = NO;
topView.backgroundColor = kColorWithHex(CP_BACKGROUD_COLOR);
// Container view
self.containerView = [[UIView alloc] init];
self.containerView.backgroundColor = [UIColor whiteColor];
self.containerView.layer.cornerRadius = kCornerRadius;
self.containerView.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
self.containerView.clipsToBounds = YES;
self.containerView.transform = CGAffineTransformMakeTranslation(0, kPopupHeight);
[self.containerView addSubview:topView];
[self.view addSubview:self.containerView];
//[self.view addSubview:self.containerView];
// Title bar
UIImageView *iconImageView = self.appIcon;
iconImageView.image = kGetImg(@"miniapp_default_icon");
iconImageView.tintColor = [UIColor blackColor];
//[self.containerView addSubview:iconImageView];
NSString *iconUrl = self.miniAppInfo[@"miniAppIcon"] ?: @"";
if (![iconUrl isEqualToString:@""]) {
[iconImageView loadUrl:iconUrl];
}
[topView addSubview:iconImageView];
UILabel *titleLabel = [[UILabel alloc] init];
NSString *miniAppName = self.miniAppInfo[@"miniAppName"] ?: @"Minapp Demo";
titleLabel.text = miniAppName;
titleLabel.font = [UIFont systemFontOfSize:17 weight:UIFontWeightMedium];
titleLabel.textColor = [UIColor blackColor];
//[self.containerView addSubview:titleLabel];
[topView addSubview:titleLabel];
// CollectionView
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.itemSize = CGSizeMake(kItemWid, kItemHei);
layout.minimumInteritemSpacing = kItemSpacing;
layout.minimumLineSpacing = kItemSpacing;
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
self.collectionView.backgroundColor = [UIColor clearColor];
self.collectionView.showsHorizontalScrollIndicator = NO;
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
//[self.containerView addSubview:self.collectionView];
[topView addSubview:self.collectionView];
// Cancel button
self.cancelButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.cancelButton setTitle:kLocalString(@"mini_app_cancel") forState:UIControlStateNormal];
[self.cancelButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
self.cancelButton.titleLabel.font = kFont(17);
self.cancelButton.backgroundColor = UIColor.whiteColor;
self.cancelButton.layer.cornerRadius = 8.0;
[self.cancelButton addTarget:self action:@selector(cancelAction) forControlEvents:UIControlEventTouchUpInside];
[self.containerView addSubview:self.cancelButton];
// Title bar element constraints
iconImageView.translatesAutoresizingMaskIntoConstraints = NO;
titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
// Icon constraints
[iconImageView.leadingAnchor constraintEqualToAnchor:topView.leadingAnchor constant:wPix(24)],
[iconImageView.topAnchor constraintEqualToAnchor:topView.topAnchor constant:hPix(16)],
[iconImageView.widthAnchor constraintEqualToConstant:kIconSize],
[iconImageView.heightAnchor constraintEqualToConstant:kIconSize],
// Title constraints
[titleLabel.leadingAnchor constraintEqualToAnchor:iconImageView.trailingAnchor constant:wPix(8)],
[titleLabel.centerYAnchor constraintEqualToAnchor:iconImageView.centerYAnchor],
[titleLabel.trailingAnchor constraintLessThanOrEqualToAnchor:topView.trailingAnchor constant:-wPix(24)]
]];
}
- (void)setupConstraints {
self.containerView.translatesAutoresizingMaskIntoConstraints = NO;
self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;
self.cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
UIView* topView = self.collectionView.superview;
[NSLayoutConstraint activateConstraints:@[
[topView.topAnchor constraintEqualToAnchor:self.containerView.topAnchor],
[topView.leftAnchor constraintEqualToAnchor:self.containerView.leftAnchor],
[topView.rightAnchor constraintEqualToAnchor:self.containerView.rightAnchor ],
// Container view constraints
[self.containerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.containerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.containerView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[self.containerView.heightAnchor constraintEqualToConstant:kPopupHeight],
// CollectionView constraints
[self.collectionView.topAnchor constraintEqualToAnchor:topView.topAnchor constant:hPix(60)],
[self.collectionView.leadingAnchor constraintEqualToAnchor:topView.leadingAnchor constant:wPix(24)],
[self.collectionView.trailingAnchor constraintEqualToAnchor:topView.trailingAnchor constant:-wPix(24)],
[self.collectionView.bottomAnchor constraintEqualToAnchor:topView.bottomAnchor],
// Cancel button constraints
[self.cancelButton.topAnchor constraintEqualToAnchor:topView.bottomAnchor constant:0],
[self.cancelButton.leadingAnchor constraintEqualToAnchor:self.containerView.leadingAnchor constant:wPix(16)],
[self.cancelButton.trailingAnchor constraintEqualToAnchor:self.containerView.trailingAnchor constant:-wPix(16)],
[self.cancelButton.heightAnchor constraintEqualToConstant:kCancelButtonHeight],
[self.cancelButton.bottomAnchor constraintEqualToAnchor:self.containerView.bottomAnchor constant:-kCancelButtonBottomMargin]
]];
}
#pragma mark - CollectionView DataSource & Delegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.items.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
UIView* c;
UIImageView* iconView;
UILabel* titleLabel;
CPMenuItem* item = self.items[indexPath.item];
if (![cell.contentView viewWithTag:CPMENU_CELL_ICON_CONTAINER_TAG]) {
c = [UIView new];
c.tag = 1234;
c.backgroundColor = UIColor.whiteColor;
c.layer.cornerRadius = 12;
[c setClipsToBounds:YES];
[cell.contentView addSubview:c];
// Icon
iconView = [[UIImageView alloc] init];
iconView.tag = CPMENU_CELL_ICON_TAG;
iconView.image = kGetImg(item.imgName);
iconView.tintColor = [UIColor whiteColor];
iconView.contentMode = UIViewContentModeScaleAspectFit;
[c addSubview:iconView];
// Title
titleLabel = [[UILabel alloc] init];
titleLabel.tag = CPMENU_CELL_TITLE_TAG;
titleLabel.text = item.title;
titleLabel.font = kFont(11);
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.textColor = kColorWithHex(@"#81848F");
titleLabel.numberOfLines = 2;
[cell.contentView addSubview:titleLabel];
// Constraints
c.translatesAutoresizingMaskIntoConstraints = NO;
iconView.translatesAutoresizingMaskIntoConstraints = NO;
titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[c.topAnchor constraintEqualToAnchor:cell.contentView.topAnchor],
[c.centerXAnchor constraintEqualToAnchor:cell.contentView.centerXAnchor],
[c.widthAnchor constraintEqualToConstant:wPix(56)],
[c.heightAnchor constraintEqualToConstant:hPix(56)],
// Icon constraints
[iconView.centerYAnchor constraintEqualToAnchor:c.centerYAnchor],
[iconView.centerXAnchor constraintEqualToAnchor:c.centerXAnchor],
[iconView.widthAnchor constraintEqualToConstant:wPix(32)],
[iconView.heightAnchor constraintEqualToConstant:wPix(32)],
// Title constraints
[titleLabel.topAnchor constraintEqualToAnchor:c.bottomAnchor constant:hPix(8)],
[titleLabel.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor],
[titleLabel.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor],
[titleLabel.bottomAnchor constraintLessThanOrEqualToAnchor:cell.contentView.bottomAnchor]
]];
// Background style
cell.contentView.backgroundColor = kColorWithHex(CP_BACKGROUD_COLOR);
cell.contentView.layer.cornerRadius = 8.0;
cell.contentView.clipsToBounds = YES;
}else{
c = [cell.contentView viewWithTag:CPMENU_CELL_ICON_CONTAINER_TAG];
iconView = [c viewWithTag:CPMENU_CELL_ICON_TAG];
titleLabel = [cell.contentView viewWithTag:CPMENU_CELL_TITLE_TAG];
iconView.image = kGetImg(item.imgName);
titleLabel.text = item.title;
}
//Unavailable state
if (item.status == 0 ){
titleLabel.alpha = iconView.alpha = 0.3;
}else if (item.status == 2){
iconView.image = kGetImg(item.vice_imgName);
titleLabel.text = item.vice_title;
}
if (!item.isAvailable) {
titleLabel.alpha = iconView.alpha = 0.3;
}
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(kItemWid, kItemHei);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, 0, 0, 0);
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
CPMenuItem* item = self.items[indexPath.item];
if (item.status == 0) {
return;
}
if (!item.isAvailable) {
return;
}
NSString *appId = self.miniAppInfo[@"miniAppId"] ?: @"";
NSString * code = @"";
switch (item.key) {
//Favorites
case MenuItemKeyAddToFav:
{
code = EMAS_CANCEL;
id<EMASMiniAppFavoriteService> favService = [[EMASServiceManager sharedInstance] serviceForProtocol:@"EMASMiniAppFavoriteService"];
if (favService){
BOOL isFavorite = [favService isFavorite:appId];
__weak typeof(self) weakSelf = self;
if (!isFavorite) {
[favService addToFavorites:appId completion:^(NSDictionary * _Nonnull result) {
[CPCommonTool showToast:kLocalStringWithValue(@"miniapp_more_favorite_success", @"Added to favorites")];//miniapp_more_favorite_success
}];
}else{
if(favService){
[favService removeFromFavorites:appId completion:^(NSDictionary * _Nonnull result) {
[CPCommonTool showToast:kLocalStringWithValue(@"miniapp_more_remove_favorite_success", @"Removed from my favorites")];
}];
}
}
};
}
break;
//Settings
case MenuItemKeySettings:
{
//Navigate to the settings page
code = EMAS_OPEN_SETTING;
}
break;
//Copy link
case MenuItemKeyCopyLink:
{
code = EMAS_CANCEL;
NSString *urlStr = self.miniAppInfo[@"miniAppUrl"];
if (urlStr && ![urlStr isEqualToString:@""]) {
UIPasteboard * pasteboard = [UIPasteboard generalPasteboard];
pasteboard.string = urlStr;
[CPCommonTool showToast:kLocalStringWithValue(@"miniapp_more_copy_success", @"Copied")];
}
}
break;
//Share
case MenuItemKeyShare:
{
__weak typeof(self) weakself = self;
UIViewController* vc = self.presentingViewController;
NSString* url = self.miniAppInfo[@"miniAppUrl"];
[self dismissViewControllerWithCompletion:^{
__strong typeof(weakself) strongSelf = weakself;
strongSelf.completionBlock(@{@"code":EMAS_CANCEL});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongSelf showShareWithString:url showIn:vc];
});
}];
}
break;
//About Us
case MenuItemKeyAboutUs:
{
code = EMAS_OPEN_ABOUT;
}
break;
//Refresh
case MenuItemKeyRefresh:
{
code = EMAS_RESTART_MINIAPP;
}
default:
break;
}
if (self.completionBlock && code && code .length != 0) {
self.completionBlock(@{@"code":code});
}
}
#pragma mark - Private
-(void)showShareWithString:(NSString*)string showIn:(UIViewController*)showVC{
if (!showVC) {
return;
}
NSString* value = nil;
id<EMASMiniAppMoreService> moreService = [[EMASServiceManager sharedInstance] serviceForProtocol:@"EMASMiniAppMoreService"];
if([moreService conformsToProtocol:@protocol(EMASMiniAppMoreService)]){
if ([moreService respondsToSelector:@selector(share:)]) {
value = [moreService share: self.miniAppInfo];
}
}
NSString* _url = value;
if (_url && ![_url isEqualToString:@""]) {
NSMutableArray *items = [NSMutableArray new];
[items addObject:_url];
UIActivityViewController *activityVC = [[UIActivityViewController alloc]
initWithActivityItems:items
applicationActivities:nil];
//iPad Compatible
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
activityVC.popoverPresentationController.sourceView = self.view;
activityVC.popoverPresentationController.sourceRect = CGRectMake(CGRectGetMidX(self.view.bounds),
CGRectGetMidY(self.view.bounds),
0, 0);
}
activityVC.completionHandler = ^(UIActivityType _Nullable activityType, BOOL completed) {
if (completed) {
[CPCommonTool showToast:kLocalStringWithValue(@"miniapp_more_share_success", @"Share Successful")];
}
};
UIViewController* vc = showVC;
while (vc.presentedViewController) {
vc = vc.presentedViewController;
}
[vc presentViewController:activityVC animated:YES completion:nil];
}else {
NSLog(@"empty share url");
}
}
#pragma mark - Actions
- (void)cancelAction {
__weak typeof(self) weakSelf = self;
[self dismissViewControllerWithCompletion:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf.completionBlock) {
strongSelf.completionBlock(@{@"code":EMAS_CANCEL});
}
}];
}
- (void)dismissViewControllerWithCompletion:(void (^)(void))finish{
[UIView animateWithDuration:0.3 animations:^{
self.view.backgroundColor = [UIColor clearColor];
self.containerView.transform = CGAffineTransformMakeTranslation(0, kPopupHeight);
} completion:^(BOOL finished) {
[self dismissViewControllerAnimated:NO completion:^{
if (finish) {
finish();
}
}];
}];
}
- (void)dismissViewController {
[self dismissViewControllerWithCompletion:nil];
}
#pragma mark - Setter
-(UIImageView*)appIcon{
if (!_appIcon) {
_appIcon = [UIImageView new];
}
return _appIcon;
}
@end
6. Implementar SettingViewController
#import "CPSettingPage.h"
#import "CPSettingTableViewCell.h"
#import "CPCommonTool.h"
#import <EMASMiniAppService/EMASPluginEnv.h>
#import <EMASMiniAppService/EMASIAuthService.h>
#import <EMASMiniAppService/EMASResultCodeConstants.h>
@interface CPSettingPage () <UITableViewDataSource,UITableViewDelegate,CPAuthChangeDelegate>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSArray *dataArray; // Data
@property (nonatomic, strong) NSDictionary *miniAppInfoDict;
@end
@implementation CPSettingPage
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setupData];
[self setupTableView];
}
- (void)miniAppInfo:(NSDictionary *)info {
self.miniAppInfoDict = info;
}
- (void)setupData {
// Initialize data source
NSString *userId = self.miniAppInfoDict[@"userId"] ?: @"";
NSString *miniAppId = self.miniAppInfoDict[@"miniAppId"] ?: @"";
id <EMASIAuthService> authService = EMASPluginEnv.shareInstance.getContainerContext.getAuthService;
NSDictionary *dic = [authService queryAuthInfo:userId miniAppId:miniAppId];
NSMutableArray* arr = @[].mutableCopy;
CPAuthItem* item = [CPAuthItem new];
item.authType = CPAuthTypeLOC;
item.title = kLocalString(@"miniapp_location");
item.isAuth = [[dic objectForKey:@"location"] boolValue];
item.scope = @"location";
[arr addObject:item];
item = [CPAuthItem new];
item.authType = CPAuthTypeCAM;
item.title = kLocalString(@"miniapp_camera");
item.isAuth = [[dic objectForKey:@"camera"] boolValue];
item.scope = @"camera";
[arr addObject:item];
item = [CPAuthItem new];
item.authType = CPAuthTypeALBUM;
item.title = kLocalString(@"miniapp_album");
item.isAuth = [[dic objectForKey:@"album"] boolValue];
item.scope = @"album";
[arr addObject:item];
item = [CPAuthItem new];
item.authType = CPAuthTypeBLE;
item.title = kLocalString(@"miniapp_bluetooth");
item.isAuth = [[dic objectForKey:@"bluetooth"] boolValue];
item.scope = @"bluetooth";
[arr addObject:item];
item = [CPAuthItem new];
item.authType = CPAuthTypeMIC;
item.title = kLocalString(@"miniapp_microphone");
item.isAuth = [[dic objectForKey:@"microphone"] boolValue];
item.scope = @"microphone";
[arr addObject:item];
item = [CPAuthItem new];
item.authType = CPAuthTypeCONT;
item.title = kLocalString(@"miniapp_contacts");
item.isAuth = [[dic objectForKey:@"contacts"] boolValue];
item.scope = @"contacts";
[arr addObject:item];
item = [CPAuthItem new];
item.authType = CPAuthTypeFILES;
item.title = kLocalString(@"miniapp_file");
item.isAuth = [[dic objectForKey:@"file"] boolValue];
item.scope = @"file";
[arr addObject:item];
item = [CPAuthItem new];
item.authType = CPAuthTypeCALLS;
item.title = kLocalString(@"miniapp_call");
item.isAuth = [[dic objectForKey:@"call"] boolValue];
item.scope = @"call";
[arr addObject:item];
item = [CPAuthItem new];
item.authType = CPAuthTypeVIBRATE;
item.title = kLocalString(@"miniapp_vibrate");
item.isAuth = [[dic objectForKey:@"vibrate"] boolValue];
item.scope = @"vibrate";
[arr addObject:item];
item = [CPAuthItem new];
item.authType = CPAuthTypeSCREEN;
item.title = kLocalString(@"miniapp_screen");
item.isAuth = [[dic objectForKey:@"screen"] boolValue];
item.scope = @"screen";
[arr addObject:item];
self.dataArray = arr.copy;
}
- (void)goBack {
if (self.completionBlock) {
self.completionBlock(@{@"code":EMAS_CANCEL});
}
}
- (void)setupTableView {
//Header view
UIView *topView = [[UIView alloc] initWithFrame:CGRectZero];
topView.translatesAutoresizingMaskIntoConstraints = NO;
//miniapp_back
topView.backgroundColor = [UIColor whiteColor];
UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
backBtn.translatesAutoresizingMaskIntoConstraints = NO;
[backBtn setImage:[CPCommonTool getImageByName:(@"miniapp_back")] forState:UIControlStateNormal];
[backBtn addTarget:self action:@selector(goBack) forControlEvents:UIControlEventTouchUpInside];
UILabel *titleLable = [[UILabel alloc] initWithFrame:CGRectZero];
titleLable.translatesAutoresizingMaskIntoConstraints = NO;
titleLable.text = kLocalString(@"miniapp_settings_title");
titleLable.font = [UIFont systemFontOfSize:16.0 weight:UIFontWeightMedium];
titleLable.textAlignment = NSTextAlignmentCenter;
titleLable.textColor = [UIColor blackColor];
[self.view addSubview:topView];
[topView addSubview:titleLable];
[topView addSubview:backBtn];
CGFloat statusbar_h = UIApplication.sharedApplication.statusBarFrame.size.height;
CGFloat h = statusbar_h + 44.0;
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.tableView.translatesAutoresizingMaskIntoConstraints = NO;
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.tableView registerClass:[CPSettingTableViewCell class] forCellReuseIdentifier:@"cell"];
[self.view addSubview:self.tableView];
self.tableView.separatorInset = UIEdgeInsetsZero;
self.tableView.layoutMargins = UIEdgeInsetsZero;
if (@available(IOS 15.0, *)) {
self.tableView.sectionHeaderTopPadding = 0;
}
// Create container view
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 0)];
headerView.translatesAutoresizingMaskIntoConstraints = NO;
headerView.backgroundColor = kColorWithHex(CP_BACKGROUD_COLOR);
NSString *miniAppName = self.miniAppInfoDict[@"miniAppName"] ?: @"MiniApp Demo";
// Create text label
UILabel *headerLabel = [[UILabel alloc] init];
headerLabel.numberOfLines = 0;
headerLabel.text = [NSString stringWithFormat:kLocalString(@"miniapp_settings_allow_tip"), miniAppName];
headerLabel.textColor = kColorWithHex(@"#81848F");
headerLabel.backgroundColor = [UIColor clearColor];
[headerLabel setFont:kFont(13)];
headerLabel.translatesAutoresizingMaskIntoConstraints = NO;
[headerView addSubview:headerLabel];
[self.tableView setTableHeaderView:headerView];
[NSLayoutConstraint activateConstraints:@[
[headerView.widthAnchor constraintEqualToAnchor:self.view.widthAnchor]
]];
[NSLayoutConstraint activateConstraints:@[
[topView.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:0],
[topView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:0],
[topView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:0],
[topView.heightAnchor constraintEqualToConstant:h],
[titleLable.widthAnchor constraintEqualToConstant:200],
[titleLable.heightAnchor constraintEqualToConstant:44.0],
[titleLable.topAnchor constraintEqualToAnchor:topView.topAnchor constant:statusbar_h],
[titleLable.centerXAnchor constraintEqualToAnchor: topView.centerXAnchor],
[backBtn.leadingAnchor constraintEqualToAnchor:topView.leadingAnchor constant:0],
[backBtn.widthAnchor constraintEqualToConstant:64],
[backBtn.heightAnchor constraintEqualToAnchor:titleLable.heightAnchor],
[backBtn.centerYAnchor constraintEqualToAnchor:titleLable.centerYAnchor],
[headerLabel.topAnchor constraintEqualToAnchor:headerView.topAnchor constant:hPix(16)],
[headerLabel.leadingAnchor constraintEqualToAnchor:headerView.leadingAnchor constant:wPix(16)],
[headerLabel.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor constant:wPix(-16)],
[headerLabel.bottomAnchor constraintEqualToAnchor:headerView.bottomAnchor constant:hPix(-16)],
[self.tableView.topAnchor constraintEqualToAnchor:topView.bottomAnchor constant:0],
[self.tableView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:0],
[self.tableView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:0],
[self.tableView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:0]
]];
[headerView setNeedsLayout];
[headerView layoutIfNeeded];
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CPSettingTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.model = self.dataArray[indexPath.row];
cell.delegate = self;
return cell;
}
#pragma mark - CPAuthChangeDelegate
-(void)onSwitchValueChange:(CPAuthItem*)authItem On:(BOOL)on{
[self updateMiniAppPermission:authItem isAuth:on];
[self.tableView reloadData];
}
- (void)updateMiniAppPermission:(CPAuthItem *)authItem isAuth:(BOOL)isAuth {
id <EMASIAuthService> authService = [EMASPluginEnv.shareInstance.getContainerContext getAuthService];
NSString *userId = self.miniAppInfoDict[@"userId"] ?: @"";
NSString *miniAppId = self.miniAppInfoDict[@"miniAppId"] ?: @"";
if (isAuth) {
BOOL isSuccess = [authService saveAuth:authItem.scope userId:userId miniAppId:miniAppId];
BOOL authed = isSuccess ? isAuth : !isAuth;
authItem.isAuth = authed;
}else {
BOOL isSuccess = [authService revokeAuth:authItem.scope userId:userId miniAppId:miniAppId];
BOOL authed = isSuccess ? isAuth : !isAuth;
authItem.isAuth = authed;
}
}
@end
7. Implementar FavoriteViewController
#import "CPComfirmViewController.h"
#import "CPCommonTool.h"
#import <EMASMiniAppService/EMASPluginEnv.h>
#import <EMASMiniAppService/EMASResultCodeConstants.h>
#import <EMASServiceManager/EMASServiceManager.h>
#import <EMASMiniAppApi/EMASMiniAppFavoriteService.h>
@implementation CPCfmViewConfig
@end
@interface CPComfirmViewController()
@property (nonatomic,strong) UIView *maskView;
@property (nonatomic,strong) NSLayoutConstraint *bottomConstraint;
@property (nonatomic,strong) UIButton *infoButton;
@property (nonatomic,strong) UIImageView *icon;
@property (nonatomic,strong) UILabel *appLabel;
@property (nonatomic,strong) UILabel *titleLabel;
@property (nonatomic,strong) UILabel *descLabel;
@property (nonatomic,strong) UIView *container;
@end
@implementation CPComfirmViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
[self loadData];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Force layout update
self.bottomConstraint.constant = 0;
// Execute display animation
[UIView animateWithDuration:0.3 animations:^{
self.maskView.alpha = 1;
[self.view layoutIfNeeded];
}];
}
/*
@"miniAppId": appid,
@"miniAppName": appName,
@"miniAppIcon": iconUrl,
@"miniAppVersion": version,
@"miniAppSlogan": slogan,
@"miniAppDescription": descrip
*/
- (void)setupUI {
self.view.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.5];
self.maskView.alpha = 0;
UIView* container = [UIView new];
container.backgroundColor = UIColor.whiteColor;
container.layer.cornerRadius = 12;
container.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
container.translatesAutoresizingMaskIntoConstraints = NO;
self.container = container;
[self.view addSubview:container];
// Info button
_infoButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_infoButton setImage:kGetImg(@"miniapp_aboutus") forState:UIControlStateNormal];
_infoButton.translatesAutoresizingMaskIntoConstraints = NO;
[_infoButton addTarget:self action:@selector(moreMiniAppInfo) forControlEvents:UIControlEventTouchUpInside];
[container addSubview:_infoButton];
_infoButton.hidden = YES;
// Icon and app name layout
UIImageView *icon = [UIImageView new];
icon.image = kGetImg(@"miniapp_default_icon");
icon.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:icon];
self.icon = icon;
NSString *iconUrl = self.config.miniAppInfo[@"miniAppIcon"] ?: @"";
if (![iconUrl isEqualToString:@""]) {
[self.icon loadUrl:iconUrl];
}
UILabel *appLabel = [UILabel new];
self.appLabel = appLabel;
NSString *miniAppName = self.config.miniAppInfo[@"miniAppName"] ?: @"Minapp Demo";
[self setAppLabelTxt:miniAppName];
appLabel.font = kFontWithWeight(15, 500);
appLabel.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:appLabel];
// Content container (using UIStackView)
UIStackView *contentStack = [[UIStackView alloc] init];
contentStack.axis = UILayoutConstraintAxisVertical;
contentStack.spacing = hPix(16);
contentStack.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:contentStack];
// Add content elements to the stack view
UILabel *titleLabel = [UILabel new];
titleLabel.text = kLocalString(@"miniapp_add_favorite_title");
titleLabel.font = kFont(17);
titleLabel.numberOfLines = 0;
[contentStack addArrangedSubview:titleLabel];
self.titleLabel = titleLabel;
UILabel *descLabel = [UILabel new];
descLabel.text = kLocalString(@"miniapp_add_favorite_info");
descLabel.font = kFont(13);
descLabel.textColor = [UIColor grayColor];
descLabel.numberOfLines = 0;
[contentStack addArrangedSubview:descLabel];
self.descLabel = descLabel;
// Button container
UIView *buttonContainer = [UIView new];
buttonContainer.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:buttonContainer];
// Reject button
UIButton *rejectBtn = [CPCommonTool getThemeLightButton];
[rejectBtn setTitle:kLocalString(@"mini_app_cancel") forState:UIControlStateNormal];
[rejectBtn addTarget:self action:@selector(rejectAction) forControlEvents:UIControlEventTouchUpInside];
[buttonContainer addSubview:rejectBtn];
// Allow button
UIButton *allowBtn = [CPCommonTool getThemeDarkButton];
[allowBtn setTitle:kLocalString(@"mini_app_ok") forState:UIControlStateNormal];
[allowBtn addTarget:self action:@selector(allowAction) forControlEvents:UIControlEventTouchUpInside];
[buttonContainer addSubview:allowBtn];
// Constraint configuration
[NSLayoutConstraint activateConstraints:@[
// Info button
[_infoButton.topAnchor constraintEqualToAnchor:container.topAnchor constant:16],
[_infoButton.trailingAnchor constraintEqualToAnchor:container.trailingAnchor constant:-wPix(24)],
[_infoButton.widthAnchor constraintEqualToConstant:24],
[_infoButton.heightAnchor constraintEqualToConstant:24],
// Icon and app name
[icon.topAnchor constraintEqualToAnchor:container.topAnchor constant:16],
[icon.leadingAnchor constraintEqualToAnchor:container.leadingAnchor constant:wPix(24)],
[icon.widthAnchor constraintEqualToConstant:wPix(24)],
[icon.heightAnchor constraintEqualToConstant:wPix(24)],
[appLabel.centerYAnchor constraintEqualToAnchor:icon.centerYAnchor],
[appLabel.leadingAnchor constraintEqualToAnchor:icon.trailingAnchor constant:wPix(8)],
[appLabel.trailingAnchor constraintEqualToAnchor:_infoButton.leadingAnchor constant:-wPix(8)],
// Content area
[contentStack.topAnchor constraintEqualToAnchor:icon.bottomAnchor constant:hPix(32)],
[contentStack.leadingAnchor constraintEqualToAnchor:container.leadingAnchor constant:wPix(24)],
[contentStack.trailingAnchor constraintEqualToAnchor:container.trailingAnchor constant:-wPix(24)],
// Button container
[buttonContainer.topAnchor constraintEqualToAnchor:contentStack.bottomAnchor constant:hPix(20)],
[buttonContainer.bottomAnchor constraintEqualToAnchor:container.bottomAnchor constant:-hPix(34)],
[buttonContainer.leadingAnchor constraintEqualToAnchor:container.leadingAnchor],
[buttonContainer.trailingAnchor constraintEqualToAnchor:container.trailingAnchor],
//[buttonContainer.heightAnchor constraintEqualToConstant:72],
// Button layout
[rejectBtn.leadingAnchor constraintEqualToAnchor:buttonContainer.leadingAnchor constant:wPix(33.5)],
[rejectBtn.topAnchor constraintEqualToAnchor:buttonContainer.topAnchor constant:hPix(20)],
[rejectBtn.heightAnchor constraintEqualToConstant:hPix(40)],
[rejectBtn.widthAnchor constraintEqualToAnchor:allowBtn.widthAnchor],
[rejectBtn.bottomAnchor constraintEqualToAnchor:buttonContainer.bottomAnchor constant:-hPix(20)],
[allowBtn.leadingAnchor constraintEqualToAnchor:rejectBtn.trailingAnchor constant:wPix(20)],
[allowBtn.trailingAnchor constraintEqualToAnchor:buttonContainer.trailingAnchor constant:-wPix(33.5)],
[allowBtn.heightAnchor constraintEqualToConstant:hPix(40)],
[allowBtn.topAnchor constraintEqualToAnchor:buttonContainer.topAnchor constant:hPix(20)],
]];
// Dynamic height calculation
[container setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical];
[container setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
}
-(void)setAppLabelTxt:(NSString*)Txt{
self.appLabel.text = [NSString stringWithFormat:@"%@ %@",Txt,kLocalString(@"miniapp_auth_apply_tip")];
}
-(void)loadData{
CPCfmViewConfig* cfn = self.config;
if (kStrExist(cfn.appName)) {
[self setAppLabelTxt:cfn.appName];
}
if (kStrExist(cfn.title)) {
self.titleLabel.text = cfn.title;
}
if (kStrExist(cfn.appIconName)) {
self.icon.image = [CPCommonTool getImageByName:cfn.appIconName];
}
if (kStrExist(cfn.content)) {
self.descLabel.text = cfn.content;
}
UIView* superView = self.view;
// Create mask layer
_maskView = self.maskView;
_maskView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
//_maskView.alpha = 0;
[superView insertSubview:_maskView belowSubview:self.container];
// Get dynamic height
[self.view layoutIfNeeded];
// Set constraints (initial position is below the bottom of the screen)
_bottomConstraint = [self.container.bottomAnchor constraintEqualToAnchor:superView.bottomAnchor constant:self.container.bounds.size.height];
[NSLayoutConstraint activateConstraints:@[
[self.container.widthAnchor constraintEqualToAnchor:superView.widthAnchor],
[self.container.leadingAnchor constraintEqualToAnchor:superView.leadingAnchor],
[self.container.trailingAnchor constraintEqualToAnchor:superView.trailingAnchor],
_bottomConstraint
]];
}
#pragma mark -View mini app info
- (void)moreMiniAppInfo {
if (self.completionBlock) {
self.completionBlock(@{@"code":EMAS_OPEN_ABOUT});
}
}
- (void)allowAction {
NSString *appId = self.config.miniAppInfo[@"miniAppId"];
if (appId && ![appId isEqualToString:@""]) {
id<EMASMiniAppFavoriteService> favService = [[EMASServiceManager sharedInstance] serviceForProtocol:@"EMASMiniAppFavoriteService"];
if (favService){
[favService addToFavorites:appId completion:^(NSDictionary * _Nonnull result) {
[CPCommonTool showToast:kLocalStringWithValue(@"miniapp_more_favorite_success", @"Added to my favorites")];
}];
}
}
__weak typeof(self) weakSelf = self;
[self dismiss:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf.completionBlock) {
strongSelf.completionBlock(@{@"code":EMAS_EXIT_MINIAPP});
}
}];
}
- (void)rejectAction {
__weak typeof(self) weakSelf = self;
[self dismiss:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf.completionBlock) {
strongSelf.completionBlock(@{@"code":EMAS_EXIT_MINIAPP});
}
}];
}
- (void)dismiss:(void(^)(void))completion{
[UIView animateWithDuration:0.3 animations:^{
self.maskView.alpha = 0;
self.container.transform = CGAffineTransformMakeTranslation(0, self.container.frame.size.height);
} completion:^(BOOL finished) {
if (completion) {
completion();
}
}];
}
- (void)dismiss {
[self dismiss:nil];
}
#pragma mark - Getter
-(UIView*)maskView{
if (!_maskView) {
_maskView = [[UIView alloc]initWithFrame:self.view.bounds];
}
return _maskView;
}
@end
8. Implementar AboutUsViewController
#import "CPAboutUsViewController.h"
#import "CPCommonTool.h"
#import <EMASMiniAppService/EMASPluginEnv.h>
#import <EMASMiniAppService/EMASResultCodeConstants.h>
@interface CPAboutUsViewController ()
@property (strong, nonatomic) UIView *containerView;
@property (strong, nonatomic) UILabel *titleLabel;
@property (strong, nonatomic) UIButton *backButton;
@property (strong, nonatomic) UIStackView *contentStackView;
@property (strong, nonatomic) NSLayoutConstraint *containerLeftConstraint;
@property (nonatomic, strong) NSArray *dataItems;
@end
@implementation CPAboutUsViewController
- (instancetype)init
{
self = [super init];
if (self) {
self.modalPresentationStyle = UIModalPresentationOverFullScreen;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self setupViews];
[self setupData];
[self.backButton addTarget:self action:@selector(dismissVC) forControlEvents:UIControlEventTouchUpInside];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self animatePresent];
}
- (void)setupViews {
self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
// Container View
self.containerView = [[UIView alloc] init];
self.containerView.backgroundColor = UIColor.whiteColor;
self.containerView.layer.cornerRadius = 12;
self.containerView.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
self.containerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.containerView];
// Title Label
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = kLocalString(@"miniapp_more_about_us_info");
self.titleLabel.font = kFontWithWeight(15, 500);
self.titleLabel.textAlignment = NSTextAlignmentCenter;
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.containerView addSubview:self.titleLabel];
// Back Button
self.backButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.backButton setImage:kGetImg(@"miniapp_back") forState:UIControlStateNormal];
self.backButton.tintColor = UIColor.blackColor;
self.backButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.containerView addSubview:self.backButton];
// Content Stack
self.contentStackView = [[UIStackView alloc] init];
self.contentStackView.axis = UILayoutConstraintAxisVertical;
self.contentStackView.spacing = hPix(16);
self.contentStackView.translatesAutoresizingMaskIntoConstraints = NO;
[self.containerView addSubview:self.contentStackView];
// Constraints
self.containerLeftConstraint = [self.containerView.leftAnchor constraintEqualToAnchor:self.view.leftAnchor constant:kScreenWid];
[NSLayoutConstraint activateConstraints:@[
[self.containerView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[self.containerView.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
self.containerLeftConstraint,
[self.titleLabel.topAnchor constraintEqualToAnchor:self.containerView.topAnchor constant:hPix(16)],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.containerView.centerXAnchor],
[self.backButton.leadingAnchor constraintEqualToAnchor:self.containerView.leadingAnchor constant:wPix(24)],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.titleLabel.centerYAnchor],
[self.contentStackView.topAnchor constraintEqualToAnchor:self.titleLabel.bottomAnchor constant:wPix(24)],
[self.contentStackView.leadingAnchor constraintEqualToAnchor:self.containerView.leadingAnchor constant:wPix(24)],
[self.contentStackView.trailingAnchor constraintEqualToAnchor:self.containerView.trailingAnchor constant:-wPix(24)],
[self.contentStackView.bottomAnchor constraintEqualToAnchor:self.containerView.bottomAnchor constant:-hPix(34)]
]];
}
- (void)miniAppInfo:(NSDictionary *)info {
if (info[@"miniAppInfo"]) {
info = info[@"miniAppInfo"];
}
NSString *miniAppName = info[@"miniAppName"] ?: @"";
NSString *slogan = info[@"miniAppSlogan"] ?: @"";
NSString *miniAppVersion = info[@"miniAppVersion"] ?: @"";
NSString *description = info[@"miniAppDescription"] ?: @"";
self.dataItems = @[
@{@"title": kLocalString(@"miniapp_about_us_miniapp_name_descrip"), @"content": miniAppName},
@{@"title": kLocalString(@"miniapp_about_us_slogan_descrip"), @"content": slogan},
@{@"title": kLocalString(@"miniapp_about_us_miniapp_version_descrip"), @"content": miniAppVersion},
@{@"title": kLocalString(@"miniapp_about_us_miniapp_descrip"), @"content": description}
];
}
- (void)setupData {
for (NSDictionary *item in self.dataItems) {
UIView *itemView = [self createItemViewWithTitle:item[@"title"] content:item[@"content"]];
[self.contentStackView addArrangedSubview:itemView];
}
}
- (UIView *)createItemViewWithTitle:(NSString *)title content:(NSString *)content {
UIView *container = [[UIView alloc] init];
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = title;
titleLabel.font = kFont(13);
titleLabel.textColor = kColorWithHex(@"#81848F");
titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:titleLabel];
UILabel *contentLabel = [[UILabel alloc] init];
contentLabel.text = content;
contentLabel.font = kFont(16);
contentLabel.numberOfLines = 0;
contentLabel.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:contentLabel];
UIView* line = [UIView new];
line.backgroundColor = kColorWithHex(CP_BACKGROUD_COLOR);
line.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:line];
[NSLayoutConstraint activateConstraints:@[
[titleLabel.topAnchor constraintEqualToAnchor:container.topAnchor],
[titleLabel.leadingAnchor constraintEqualToAnchor:container.leadingAnchor],
[titleLabel.trailingAnchor constraintEqualToAnchor:container.trailingAnchor],
[contentLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:hPix(4)],
[contentLabel.leadingAnchor constraintEqualToAnchor:container.leadingAnchor],
[contentLabel.trailingAnchor constraintEqualToAnchor:container.trailingAnchor],
[contentLabel.bottomAnchor constraintEqualToAnchor:container.bottomAnchor constant: -hPix(16)],
[line.bottomAnchor constraintEqualToAnchor:container.bottomAnchor],
[line.widthAnchor constraintEqualToAnchor:container.widthAnchor],
[line.heightAnchor constraintEqualToConstant:1],
]];
return container;
}
- (void)animatePresent {
self.containerLeftConstraint.constant = 0;
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
}
- (void)dismissVC {
self.containerLeftConstraint.constant = kScreenWid;
if (self.navigationController) {
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
//[self.navigationController popViewControllerAnimated:YES];
if (self.completionBlock) {
self.completionBlock(@{@"code":EMAS_CANCEL});
}else{
[self.navigationController popViewControllerAnimated:YES];
}
}];
}else{
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
//[self dismissViewControllerAnimated:NO completion:nil];
if (self.completionBlock) {
self.completionBlock(@{@"code":EMAS_CANCEL});
}else{
[self dismissViewControllerAnimated:NO completion:nil];
}
}];
}
}
@end
3.1.4 Registrar plugin
3.1.4.1 No Android
private void initWindVaneMiniApp() {
// 1. config the MiniAppService params
MiniAppInitConfig config = new MiniAppInitConfig.Builder()
.setUseWindVane(true)
.setUseUniApp(false)
.setHost("poc.superapp-intl.com")
.setAppCode("YourAppCode")
.setAccessKey("YourAccessKey")
.setSecretKey("YourSecretKey")
.build();
// 2. create miniAppService object and init it
IMiniAppService miniAppService = new MiniAppService();
miniAppService.initialize(application, config);
...
// 2. register plugin
miniAppService.registerPlugin("CustomUIPlugin", CustomUIPlugin(this));
}
3.1.4.2 No iOS
private func configMiniAppContainer() {
// 1. config the MiniAppService params
let miniAppInitConfig = EMASMiniAppInitConfig()
miniAppInitConfig.accessKey = "YourAccessKey";
miniAppInitConfig.secretKey = "YourSecretKey";
miniAppInitConfig.host = "poc.superapp-intl.com";
miniAppInitConfig.appCode = "YourAppCode";
miniAppInitConfig.useUniApp = false
miniAppInitConfig.useWindVane = true
//2. create miniAppService object and init it
let miniAppService = EMASMiniAppServiceImpl();
miniAppService.initialize(miniAppInitConfig)
EMASServiceManager.sharedInstance().registerServiceProtocol("EMASMiniAppService", impClass: "EMASMiniAppServiceImpl", target: miniAppService)
// 3. register plugin
let uiPlugin = EMASClientCustomUIPlugin()
miniAppService.registerPlugin("ClientCustomUIPlugin", plugin: uiPlugin)
}
3,2 Cenário Flutter
3.2.0 Fluxograma de interação do Flutter

3.2.1 Importar pacotes
// in build.gradle
implementation "com.aliyun.emas.suite.foundation:mini-app-adapter:1.8.14"
implementation "com.aliyun.emas.suite.foundation:windvane-mini-app:1.8.14"
implementation "com.aliyun.emas.suite.foundation:mini-app-plugin-base:1.8.14"
// in podfile
pod 'EMASMiniAppAdapter', '1.1.4'
pod 'EMASWindVaneMiniApp', '1.2.5'
//if use bluetooth feature please pod WindVaneBluetooth
pod 'WindVaneBluetooth', '1.0.2'
3.2.2 Implementar FlutterAuthDialogFragment
Para utilizar uma página Flutter nos diálogos de autorização, implemente primeiro o contêiner FlutterFragment.
public class FlutterAuthDialogFragment extends FlutterFragment {
private static final String TAG = FlutterAuthDialogFragment.class.getName();
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_ROUTE = "route";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mRoute;
private String mParam2;
private MethodChannel mMethodChannel;
public FlutterAuthDialogFragment(String route) {
mRoute = route;
}
public static FlutterAuthDialogFragment newInstance(String route, String dartEntrypoint) {
FlutterAuthDialogFragment flutterFragment = new FlutterAuthDialogFragment(route);
Bundle args = new Bundle();
args.putString(ARG_FLUTTERVIEW_TRANSPARENCY_MODE, TransparencyMode.transparent.name());
flutterFragment.setArguments(args);
return flutterFragment;
}
@Nullable
@Override
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
FlutterEngine flutterEngine = new FlutterEngine(context);
flutterEngine.getNavigationChannel().setInitialRoute(mRoute);
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
);
return flutterEngine;
}
@Override
public void onAttach(@NonNull android.content.Context context) {
super.onAttach(context);
initializeMethodChannel();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mRoute = getArguments().getString(ARG_ROUTE);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
private void initializeMethodChannel() {
FlutterEngine flutterEngine = getFlutterEngine();
if (flutterEngine == null) {
Log.e("FlutterFragment", "FlutterEngine not initialized");
return;
}
mMethodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), "flutter_auth_dialog");
mMethodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
IAuthService authService = PluginEnv.getInstance().getContainerContext().getAuthService();
if (FlutterUIPluginConstant.FLUTTER_APPROVAL_AUTH_ACTION.equals(call.method)) {
Log.i(TAG, "approval auth");
Map<String, Object> arguments = (Map<String, Object>)call.arguments;
String permission = (String)arguments.get("permission");
String userId = (String)arguments.get("userId");
String miniAppId = (String)arguments.get("miniAppId");
// Handle approval authorization actions
authService.saveAuth(FlutterAuthDialogFragment.this.getActivity(), permission, userId, miniAppId);
// return 'result' to flutter code
result.success(true);
} else if (FlutterUIPluginConstant.FLUTTER_CHECK_AUTH_ACTION.equals(call.method)) {
Log.i(TAG, "check auth");
Map<String, Object> arguments = (Map<String, Object>)call.arguments;
String permission = (String)arguments.get("permission");
String userId = (String)arguments.get("userId");
String miniAppId = (String)arguments.get("miniAppId");
// Handle check authorization actions
boolean isAuthorized = authService.isAuthorized(FlutterAuthDialogFragment.this.getActivity(), permission, userId, miniAppId);
// return 'result' to flutter code
result.success(isAuthorized);
} else {
result.notImplemented();
}
}
});
}
@Override
public void onDestroy() {
super.onDestroy();
if (mMethodChannel != null) {
mMethodChannel.setMethodCallHandler(null);
}
}
}
iOS
import UIKit
import EMASMiniAppService
class EMASAliAuthUIViewController: FlutterViewController {
var messageChannel: FlutterMethodChannel?
var authBlock: (([AnyHashable : Any]) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.init(white: 0.0, alpha: 0.5)
//flutter_auth_dialog
}
func setAuthInfo(_ dic: [AnyHashable : Any]) {
self.messageChannel = FlutterMethodChannel(name: "flutter_auth_dialog", binaryMessenger: self.binaryMessenger)
self.messageChannel?.setMethodCallHandler({ [weak self] call, result in
print("------\(call.method)")
if call.method == "flutter_approval_auth_action" {
self?.selectScope(call: call, result: result)
}
})
}
func selectScope(call: FlutterMethodCall, result: FlutterResult) {
guard let arguments = call.arguments as? NSDictionary, let permission = arguments["permission"] as? String else {
self.authBlock?(["code": EMAS_ERROR, "msg": "not get permission"])
result(nil)
return
}
print("------\(arguments)")
self.authBlock?(["code": EMAS_CONFIRM, "msg": "", "data": [permission: true]])
result(nil);
}
}
3.2.3 Implementar a página de autenticação do Flutter
import 'package:flutter/material.dart';
import 'package:fluttertest/windvane.dart';
class AuthListPage extends StatefulWidget {
const AuthListPage({super.key});
@override
State<StatefulWidget> createState() {
return AuthListPageState();
}
}
class AuthListPageState extends State<AuthListPage> {
final List<String> permissionsItem = [
"location",
"album",
"camera",
"bluetooth",
"contacts",
"microphone",
"file",
"call",
"vibrate",
"screen",
];
final List<bool> approvalItem = [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
];
@override
Widget build(BuildContext context) {
return SizedBox(
height: 1000,
child: ListView.builder(
itemCount: permissionsItem.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(permissionsItem[index]),
onTap: () {
print("click item: ${permissionsItem[index]}");
Map<String, String> args = {
"permission": permissionsItem[index],
"userId": "123",
"miniAppId": "17856810363",
};
// step 7: user apply/refuse auth
WindVaneMiniAppManager.authChannel.invokeMethod(approvalItem[index] ? "flutter_revoke_auth_action" : "flutter_approval_auth_action", args).then((result) {
// step 9: receive auth result
approvalItem[index] = result;
// step 10: refresh page
setState(() {});
});
},
);
},
),
);
}
}
3.2.4 Implementar o canal de métodos do Flutter
class WindVaneMiniAppManager {
static const MethodChannel authChannel = const MethodChannel("flutter_auth_dialog");
}
3.2.5 Declarar a rota da página Flutter
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: (RouteSettings settings) {
final uri = Uri.parse(settings.name ?? '/');
Map<String, String> params = uri.queryParameters;
String path = uri.path;
// step 1: register flutter auth page rou
if (path == "/ui/auth") {
return MaterialPageRoute(builder: (BuildContext context) {
return AuthListPage();
});
} else if (path == "/ui/more") {
return MaterialPageRoute(builder: (BuildContext context) {
return const MoreDialogPage();
});
} else {
...
}
},
initialRoute: 'home',
builder: EasyLoading.init(),
);
}
}
3.2.6 Implementar interfaces por meio de plugin
public class FlutterUIPlugin extends BasePlugin implements UIExtension {
public FlutterUIPlugin(Context context) {
super(context);
}
// step 2: UIService call createUserDeviceInfoAuthDialogFragment(...) to create Auth Dialog fragment
@Override
public Fragment createUserDeviceInfoAuthDialogFragment(HashMap<String, Object> params) {
// "/ui/auth" is flutter page route
FlutterFragment fragment = FlutterAuthDialogFragment.newInstance("/ui/auth", params);
return fragment;
}
...
}
iOS
import UIKit
import EMASMiniAppService
class EMASAliCustomUIPlugin: EMASBasePlugin, EMASIUIExtension {
func createUserDeviceInfoAuthDialogViewController(_ uiComponentParam: EMASUIComponentParam) -> UIViewController? {
let vc = EMASAliAuthUIViewController()
vc.setAuthInfo(uiComponentParam.params)
vc.authBlock = uiComponentParam.completionBlock
vc.setInitialRoute("/ui/auth")
return vc
}
}
3.2.7 Registrar plugin
private void initWindVaneMiniApp() {
// 1. config the MiniAppService params
MiniAppInitConfig config = new MiniAppInitConfig.Builder()
.setUseWindVane(true)
.setUseUniApp(false)
.setHost("poc.superapp-intl.com")
.setAppCode("YourAppCode")
.setAccessKey("YourAccessKey")
.setSecretKey("YourSecretKey")
.build();
// 2. create miniAppService object and init it
IMiniAppService miniAppService = new MiniAppService();
miniAppService.initialize(application, config);
...
// 2. register plugin
miniAppService.registerPlugin("FlutterUIPlugin", FlutterUIPlugin(this));
}
iOS
private func configMiniAppContainer() {
// 1. config the MiniAppService params
let miniAppInitConfig = EMASMiniAppInitConfig()
miniAppInitConfig.accessKey = "YourAccessKey";
miniAppInitConfig.secretKey = "YourSecretKey";
miniAppInitConfig.host = "poc.superapp-intl.com";
miniAppInitConfig.appCode = "YourAppCode";
miniAppInitConfig.useUniApp = false
miniAppInitConfig.useWindVane = true
//2. create miniAppService object and init it
let miniAppService = EMASMiniAppServiceImpl();
miniAppService.initialize(miniAppInitConfig)
EMASServiceManager.sharedInstance().registerServiceProtocol("EMASMiniAppService", impClass: "EMASMiniAppServiceImpl", target: miniAppService)
// 3. register plugin
let aliUIPlugin = EMASAliCustomUIPlugin()
miniAppService.registerPlugin("EMASAliCustomUIPlugin", plugin: aliUIPlugin)
}
3,3 Página semitransparente do Flutter
3.3.1 Página semitransparente
|
|
3.3.2 No arquivo 'main.dart', garanta que o plano de fundo do MaterialApp seja transparente:
void main() {
runApp(
MaterialApp(
// set transparent background
theme: ThemeData(
scaffoldBackgroundColor: Colors.transparent,
canvasColor: Colors.transparent,
),
onGenerateRoute: (RouteSettings settings) {
final uri = Uri.parse(settings.name ?? '/');
Map<String, String> params = uri.queryParameters;
String path = uri.path;
if (path == "/ui/auth") {
return MaterialPageRoute(builder: (BuildContext context) {
return AuthListPage();
});
} else {
return MaterialPageRoute(builder: (BuildContext context) {
return MyHomePage(title: settings.name.toString());
});
}
},
),
);
}
3.3.3 Exemplo de página Flutter semitransparente
class AuthListPage extends StatefulWidget {
const AuthListPage({super.key});
@override
State<StatefulWidget> createState() {
return AuthListPageState();
}
}
class AuthListPageState extends State<AuthListPage> {
final List<String> permissionsItem = [
"location",
"album",
"camera",
"bluetooth",
"contacts",
"microphone",
"file",
"call",
"vibrate",
"screen",
];
final List<bool> approvalItem = [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
];
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: Container(
// set semi transparent value
color: Colors.black.withOpacity(0.65),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 50,
color: Colors.white,
alignment: Alignment.center,
child: const Text("The Auth List on the semi transparent page"),
),
Container(
height: 300,
color: Colors.white,
child: ListView.builder(
itemCount: permissionsItem.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(permissionsItem[index]),
onTap: () {
print("click item: ${permissionsItem[index]}");
Map<String, String> args = {
"permission": permissionsItem[index],
"userId": "123",
"miniAppId": "17856810363",
};
// step 7: user apply/refuse auth
WindVaneMiniAppManager.authChannel.invokeMethod(approvalItem[index] ? "flutter_revoke_auth_action" : "flutter_approval_auth_action", args).then((result) {
// step 9: receive auth result
approvalItem[index] = result;
// step 10: refresh page
setState(() {});
});
},
);
},
),
),
],
),
],
);
}
}
3.3.4 Criar FlutterFragment e definir transparência
3.3.4.1 No Android
public static FlutterAuthDialogFragment newInstance(String route, String dartEntrypoint) {
FlutterAuthDialogFragment flutterFragment = new FlutterAuthDialogFragment(route);
Bundle args = new Bundle();
args.putString(ARG_FLUTTERVIEW_TRANSPARENCY_MODE, TransparencyMode.transparent.name());
flutterFragment.setArguments(args);
return flutterFragment;
}
3.3.4.2 No iOS
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.init(white: 0.0, alpha: 0.5)
//flutter_auth_dialog
}
3.3.5 Referência
Consulte a branch "feature/flutter_page_in_native" no projeto flutter_windvane_demo.
4 Interfaces comuns
4,1 Interface de autenticação
4.1.1 No Android
4.1.1.1 Interface de autenticação no SDK
public interface IAuthService {
public boolean saveAuth(Context context, String scope, String userId, String miniAppId);
public boolean saveAuthList(Context context, List<String> scopes, String userId, String miniAppId);
public boolean revokeAuth(Context context, String scope, String userId, String miniAppId);
public boolean revokeAuthList(Context context, List<String> scopes, String userId, String miniAppId);
public String queryAuthInfo(Context context, String userId, String miniAppId);
public boolean isAuthorized(Context context, String scope, String userId, String miniAppId);
}
4.1.1.2 Utilizar AuthService
IAuthService authService = PluginEnv.getInstance().getContainerContext().getAuthService();
authService.saveAuth(context, scope, userId, miniAppId);
4.1.2 No iOS
4.1.2.1 Interface de autenticação no SDK
@protocol EMASIAuthService <NSObject>
@optional
- (BOOL)saveAuth:(NSString *)scope userId:(NSString *)userId miniAppId:(NSString *)miniAppId;
- (BOOL)saveAuthList:(NSArray<NSString *> *)scopes userId:(NSString *)userId miniAppId:(NSString *)miniAppId;
- (BOOL)revokeAuth:(NSString *)scope userId:(NSString *)userId miniAppId:(NSString *)miniAppId;
- (BOOL)revokeAuthList:(NSArray<NSString *> *)scopes userId:(NSString *)userId miniAppId:(NSString *)miniAppId;
- (NSDictionary *)queryAuthInfo:(NSString *)userId miniAppId:(NSString *)miniAppId;
- (BOOL)isAuthorized:(NSString *)scope userId:(NSString *)userId miniAppId:(NSString *)miniAppId;
@end
4.1.2.2 Utilizar AuthService
EMASPluginEnv.shareInstance().getContainerContext().getAuthService().saveAuthList!(scopes, userId: userId, miniAppId: miniAppId)
4,2 Interfaces de UI
4.2.1 No Android
4.2.1.1 Interface de UI no SDK
public interface UIExtension {
// Used to create 'More' dialog, refer to 'figure 1' in the remarks for example style
public default Fragment createMoreDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Used to create 'Favorite' dialog, refer to 'figure 2' in the remarks for example style
public default Fragment createFavoriteDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Used to create 'Auth' dialog, refer to 'figure 3' in the remarks for example style
public default Fragment createUserDeviceInfoAuthDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Used to create 'Error' sub page, refer to 'figure 5' in the remarks for example style
public default Fragment createErrorPageFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Used to create 'Slash' sub page, refer to 'figure 6' in the remarks for example style
public default Fragment createSplashPageFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Used to create 'About US' dialog, refer to 'figure 8' in the remarks for example style
public default Fragment createAboutUsDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Used to create 'User Info Auth' sub page, refer to 'figure 7' in the remarks for example style
public default Fragment createUserInfoAuthPageFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
}
public class UIComponentParam {
private UIResultCallBack uiResultCallBack;
private Context context;
private HashMap<String, Object> params;
/**
* uiResultCallBack:The data returned
* data:Data sent out
* **/
public UIComponentParam(UIResultCallBack uiResultCallBack, HashMap<String, Object> data, Context context) {
this.uiResultCallBack = uiResultCallBack;
this.params = data;
this.context = context;
}
public UIResultCallBack getUiResultCallBack() {
return uiResultCallBack;
}
public HashMap<String, Object> getParams() {
return params;
}
public Context getContext() {
return context;
}
}
4.2.2 No iOS
@protocol EMASIUIExtension <NSObject>
@optional
// Used to create 'More' dialog, refer to 'figure 1' in the remarks for example style
- (nullable UIViewController *)createMoreDialogViewController:(EMASUIComponentParam *)uiComponentParam;
// Used to create 'Favorite' dialog, refer to 'figure 2' in the remarks for example style
- (nullable UIViewController *)createFavoriteDialogViewController:(EMASUIComponentParam *)uiComponentParam;
// Used to create 'Auth' dialog, refer to 'figure 3' in the remarks for example style
- (nullable UIViewController *)createUserDeviceInfoAuthDialogViewController:(EMASUIComponentParam *)uiComponentParam;
// Used to create 'Error' sub page, refer to 'figure 5' in the remarks for example style
- (nullable UIViewController *)createErrorPageViewController:(EMASUIComponentParam *)uiComponentParam;
// Used to create 'Slash' sub page, refer to 'figure 6' in the remarks for example style
- (nullable UIViewController *)createSplashPageViewController:(EMASUIComponentParam *)uiComponentParam;
// Used to create 'About US' dialog, refer to 'figure 8' in the remarks for example style
- (nullable UIViewController *)createAboutUsDialogViewController:(EMASUIComponentParam *)uiComponentParam;
- (nullable UIViewController *)createSettingsDialogViewController:(EMASUIComponentParam *)uiComponentParam;
@end
@interface EMASUIComponentParam : NSObject
@property (nonatomic, strong) NSDictionary * params;
@property (nonatomic, copy) void(^completionBlock)(NSDictionary *resultDict);
@end
FOUNDATION_EXPORT NSString * _Nonnull const EMAS_CANCEL;
FOUNDATION_EXPORT NSString * _Nonnull const EMAS_CONFIRM;
FOUNDATION_EXPORT NSString * _Nonnull const EMAS_RESTART_MINIAPP;
FOUNDATION_EXPORT NSString * _Nonnull const EMAS_EXIT_MINIAPP;
FOUNDATION_EXPORT NSString * _Nonnull const EMAS_ERROR;
FOUNDATION_EXPORT NSString * _Nonnull const EMAS_OPEN_ABOUT;
FOUNDATION_EXPORT NSString * _Nonnull const EMAS_OPEN_SETTING;
5 Nova UI padrão no SDK
|
Figura 1 Diálogo Mais |
|
|
Figura 2 Diálogo Favoritos |
|
|
Figura 3 Diálogo de autenticação |
|
|
Figura 4 Diálogo de carregamento |
|
|
Figura 5 Página de erro |
|
|
Figura 6 Tela inicial |
|
|
Figura 7 Página de autenticação de informações do usuário |
|
|
Figura 8 Diálogo Sobre nós |
|








