Kustomisasi UI Kontainer MiniApp
1 Latar Belakang & Tujuan
Kontainer MiniApp telah mengimplementasikan UI default, seperti tampilan otorisasi perangkat pengguna. Namun, beberapa klien mengharapkan penyesuaian sebagian atau seluruh UI default tersebut.



Dukungan bagi klien untuk menyesuaikan sebagian atau seluruh UI default tersedia melalui solusi berikut.
2 Arsitektur & Alur Secara Keseluruhan


3 Cara Mengembangkan
3.1 Skenario bawaan
3.1.1 Impor paket terkait
// 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 Antarmuka UI dalam SDK
3.1.2.1 Di Android
public interface UIExtension {
// Digunakan untuk membuat dialog 'More', lihat ‘figure 1’ pada catatan untuk contoh gaya
public default Fragment createMoreDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Digunakan untuk membuat dialog 'Favorite', lihat ‘figure 2’ pada catatan untuk contoh gaya
public default Fragment createFavoriteDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Digunakan untuk membuat dialog 'Auth', lihat ‘figure 3’ pada catatan untuk contoh gaya
public default Fragment createUserDeviceInfoAuthDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Digunakan untuk membuat sub halaman 'Error', lihat ‘figure 5’ pada catatan untuk contoh gaya
public default Fragment createErrorPageFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Digunakan untuk membuat sub halaman 'Slash', lihat ‘figure 6’ pada catatan untuk contoh gaya
public default Fragment createSplashPageFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Digunakan untuk membuat dialog 'About US', lihat ‘figure 8’ pada catatan untuk contoh gaya
public default Fragment createAboutUsDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Digunakan untuk membuat sub halaman 'User Info Auth', lihat ‘figure 7’ pada catatan untuk contoh gaya
public default Fragment createUserInfoAuthPageFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
}public class UIComponentParam {
private UIResultCallBack uiResultCallBack;
private Context context;
private HashMap<String, Object> params;
/**
* uiResultCallBack: Data yang dikembalikan
* data: Data yang dikirim
* **/
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";
}Cara menggunakan konstanta dalam format data Auth
CANCEL: Ketika pengguna mengklik tombol cancel pada halaman, atur
codeke konstanta ini. SDK akan menutup jendela pop-up.CONFIRM: Ketika pengguna mengklik tombol confirm pada halaman, atur
codeke konstanta ini dan kembalikan informasi relevan didata. SDK akan menutup jendela pop-up.ERROR: Ketika pemrosesan komponen gagal, atur
codeke konstanta ini dan berikan pesan kesalahan dimsg. SDK akan menutup jendela pop-up dan meneruskan pesan kesalahan ke JavaScript untuk dikembalikan ke miniapp.RESTART_MINIAPP: Ketika pengguna mengklik tombol refresh/restart pada halaman miniapp, atur
codeke konstanta ini. SDK akan membuka ulang dan memuat kembali miniapp.EXIT_MINIAPP: Ketika pengguna mengklik tombol exit pada halaman miniapp, atur
codeke konstanta ini. SDK akan menutup miniapp saat ini.
Cara menggunakan onComplete dan kode UIResultCodeConstants:
a. Di ErrorPageFragment:
Ketika pengguna mengklik tombol exit pada halaman miniapp, atur code ke EXIT_MINIAPP.
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.EXIT_MINIAPP));Ketika pengguna mengklik tombol refresh/restart pada halaman miniapp, atur code ke RESTART_MINIAPP.
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(UIResultCodeConstants.RESTART_MINIAPP));b. Di UserDeviceInfoAuthDialogFragment:
uiComponentParam.params adalah sebagai berikut:
authorize: Parameter ini diteruskan ketika miniapp memanggil wv.authorize. Jika parameter metode wv.authorize berisi "scope" dan "scopes", "scopes" digunakan terlebih dahulu; jika tidak, parameter "scope" yang digunakan. SDK akan memeriksa status otorisasi sebelumnya yang diberikan oleh miniapp dan mengatur status otorisasi untuk scope yang diminta.
appId: ID miniapp
userId: userId
miniAppInfo: informasi meta miniapp.
/*Ini sesuai dengan struktur yang diperoleh dengan kunci ’authorize‘ dalam param di UIComponentParam
{
"authorize": {"location": true/false, "camera": true/false},
"appId": appId,
"userId": userInfo.userId,
"miniAppInfo": miniAppInfo
}
Ini sesuai dengan struktur yang diperoleh sesuai dengan kunci 'miniAppInfo' dalam param di 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);
}
}Jika pengguna membatalkan otorisasi, data dalam onComplete harus diatur sebagai berikut:
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData( UIResultCodeConstants.CANCEL));Jika pengguna menolak otorisasi, data dalam onComplete harus diatur sebagai berikut:
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData( "User refuses authorization request", UIResultCodeConstants.ERROR));Jika pengguna menyetujui otorisasi, hasil otorisasi akan dikembalikan ke callback wv.authorize (menandai scope input sebagai true/false). Data dalam onComplete harus diatur sebagai berikut:
/*
finalJsObj:Format informasi sebagai berikut JsonObject:
{"location": true,"camera": false}
*Contoh:JSONObject finalJsObj=new JSONObject
*finalJsObj.put("file","true/false")
*/
uiComponentParam.getUiResultCallBack().onComplete(UIResultDataUtil.getResultData(finalJsObj, "", UIResultCodeConstants.CONFIRM));c. Di UserMoreDialogFragment
uiComponentParam.params adalah sebagai berikut:
miniAppInfo: informasi meta miniapp.
appId: ID miniapp
userId: userId
miniAppUrl: URL halaman saat ini dari miniapp
/*Ini sesuai dengan struktur yang diperoleh dari param di UIComponentParam
{
"miniAppUrl":"current page url"
"appId": appId,
"userId": userInfo.userId,
"miniAppInfo": miniAppInfo
}
Ini sesuai dengan struktur yang diperoleh sesuai dengan kunci 'miniAppInfo'
dalam param di 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);
}
}Tombol "add/remove" Favorite: Jika fungsi “Mini App Favorite” diperlukan, harap implementasikan antarmuka "IMiniAppFavoriteService".
Kode contoh sebagai berikut:
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();
}
}
}
}Tombol "setting" di UserMoreDialog digunakan untuk mengelola "User Device Info Scope", di mana pengguna dapat "approve/revoke" scope tersebut.
Halaman pengelolaan scope (UserInfoAuthPageFragment) akan dibuka ketika pengguna mengklik tombol "setting".
Catatan: Jika Anda ingin menggunakan scope kustom, Anda perlu menyesuaikan halaman tersebut. Anda dapat merujuk ke bagian 3.1.3.1.6 (AuthorizeSettingsFragment).
UserInfoAuthPageFragment:
Parameter awal untuk membuka halaman UserInfoAuthPageFragment adalah sebagai berikut:
uiComponentParam.params
miniAppInfo:informasi meta miniapp.
appId: ID miniapp
userId: userId
Tombol "copy" digunakan untuk menyalin URL halaman saat ini:
Tombol "share": Jika fungsi “share” diperlukan, harap implementasikan antarmuka "IMiniAppMoreService". Kode contoh sebagai berikut:
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);Tombol "refresh" digunakan untuk me-restart miniapp.
Jika Anda ingin membuka halaman "about us" dan "scope setting" di halaman more kustom, kode contoh sebagai berikut:
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. Di user AboutUsDialogfragment
Kode contoh merujuk ke bagian 3.1.3.1.8.
uiComponentParam.params adalah sebagai berikut:
miniAppInfo:informasi meta miniapp.
e. Di user FavoriteDialogFragment
Kode contoh merujuk ke bagian 3.1.3.1.7.
uiComponentParam.params adalah sebagai berikut:
miniAppInfo:informasi meta miniapp.
appId: ID miniapp
userId: userId
3.1.2.2 Di iOS
@protocol EMASIUIExtension <NSObject>
@optional
// Digunakan untuk membuat dialog 'More', lihat ‘figure 1’ pada catatan untuk contoh gaya
- (nullable UIViewController *)createMoreDialogViewController:(EMASUIComponentParam *)uiComponentParam;
// Digunakan untuk membuat dialog 'Favorite', lihat ‘figure 2’ pada catatan untuk contoh gaya
- (nullable UIViewController *)createFavoriteDialogViewController:(EMASUIComponentParam *)uiComponentParam;
// Digunakan untuk membuat dialog 'Auth', lihat ‘figure 3’ pada catatan untuk contoh gaya
- (nullable UIViewController *)createUserDeviceInfoAuthDialogViewController:(EMASUIComponentParam *)uiComponentParam;
// Digunakan untuk membuat sub halaman 'Error', lihat ‘figure 5’ pada catatan untuk contoh gaya
- (nullable UIViewController *)createErrorPageViewController:(EMASUIComponentParam *)uiComponentParam;
// Digunakan untuk membuat sub halaman 'Slash', lihat ‘figure 6’ pada catatan untuk contoh gaya
- (nullable UIViewController *)createSplashPageViewController:(EMASUIComponentParam *)uiComponentParam;
// Digunakan untuk membuat dialog 'About US', lihat ‘figure 8’ pada catatan untuk contoh gaya
- (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;
Cara menggunakan konstanta dalam format data Auth
EMAS_CANCEL: Ketika pengguna mengklik tombol cancel pada halaman, atur
codeke konstanta ini. SDK akan menutup jendela pop-up.EMAS_CONFIRM: Ketika pengguna mengklik tombol confirm pada halaman, atur
codeke konstanta ini dan kembalikan informasi relevan didata. SDK akan menutup jendela pop-up.EMAS_ERROR: Ketika pemrosesan komponen gagal, atur
codeke konstanta ini dan berikan pesan kesalahan dimsg. SDK akan menutup jendela pop-up dan meneruskan pesan kesalahan ke JavaScript untuk dikembalikan ke miniapp.EMAS_RESTART_MINIAPP: Ketika pengguna mengklik tombol refresh/restart pada halaman miniapp, atur
codeke konstanta ini. SDK akan membuka ulang dan memuat kembali miniapp.EMAS_EXIT_MINIAPP: Ketika pengguna mengklik tombol exit pada halaman miniapp, atur
codeke konstanta ini. SDK akan menutup miniapp saat ini.
Cara menggunakan completionBlock dan kode Constant:
a. Di ErrorPageViewController:
Ketika pengguna mengklik tombol exit pada halaman miniapp, atur code ke EMAS_EXIT_MINIAPP.
uiComponentParam.completionBlock(["code": EMAS_EXIT_MINIAPP])Ketika pengguna mengklik tombol refresh/restart pada halaman miniapp, atur code ke EMAS_RESTART_MINIAPP.
uiComponentParam.completionBlock(["code": EMAS_RESTART_MINIAPP])b. Di UserDeviceInfoAuthDialogViewController:
uiComponentParam.params adalah sebagai berikut:
authorize: Parameter ini diteruskan ketika miniapp memanggil wv.authorize. Jika parameter metode wv.authorize berisi "scope" dan "scopes", "scopes" digunakan terlebih dahulu; jika tidak, parameter "scope" yang digunakan. SDK akan memeriksa status otorisasi sebelumnya yang diberikan oleh miniapp dan mengatur status otorisasi untuk scope yang diminta.
appId: ID miniapp
userId: userId
miniAppInfo: informasi meta 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
]
*/Jika pengguna membatalkan otorisasi, data dalam completionBlock harus diatur sebagai berikut:
uiComponentParam.completionBlock(["code": EMAS_CANCEL])Jika pengguna menolak otorisasi, data dalam completionBlock harus diatur sebagai berikut:
uiComponentParam.completionBlock(["code": EMAS_CANCEL, "msg":"User cancels authorization"])Jika pengguna menyetujui otorisasi, hasil otorisasi akan dikembalikan ke callback wv.authorize (menandai scope input sebagai true/false). Data dalam completionBlock harus diatur sebagai berikut:
/*
authDic:Format informasi sebagai berikut:
["location": true,"camera": false]
*/
uiComponentParam.completionBlock(["code": EMAS_CONFIRM, "msg":"", "data":authDic])c. Di UserMoreDialogFragment
uiComponentParam.params adalah sebagai berikut:
miniAppId: ID miniapp
userId: userId
miniAppUrl: URL halaman saat ini dari miniapp
miniAppName: appName
miniAppIcon: iconUrl
miniAppSlogan: slogan
miniAppDescription: description
miniAppVersion: version
/*
Ini sesuai dengan struktur yang diperoleh dalam param di UIComponentParam.
{
"miniAppId": appId,
"miniAppName": appName,
"miniAppIcon": iconUrl,
"miniAppVersion": version,
"miniAppSlogan": slogan,
"miniAppDescription": description,
"miniAppUrl": "current page url",
"userId": userId
}
*/Tombol "add/remove" Favorite: Jika fungsi “Mini App Favorite” diperlukan, harap implementasikan "EMASMiniAppFavoriteService".
jika fungsi koleksi pengguna ada di more
#import "EMASMiniAppFavoriteServiceImpl.h"
@implementation EMASMiniAppFavoriteServiceImpl
/**
* add to favorites
* @param subAppCode ID miniapp
* @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 ID miniapp
* @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 ID miniapp
* @return int 0, add success | not 0, error code
*/
- (BOOL)isFavorite:(NSString*)subAppCode {
BOOL isFavorite = [[NSUserDefaults standardUserDefaults] objectForKey:subAppCode];
return isFavorite;
}
@end
Tombol "setting" di UserMoreDialog digunakan untuk mengelola "User Device Info Scope", di mana pengguna dapat "approve/revoke" scope tersebut.
Halaman pengelolaan scope (SettingViewController) akan dibuka ketika pengguna mengklik tombol "setting".
Catatan: Jika Anda ingin menggunakan scope kustom, Anda perlu menyesuaikan halaman tersebut. Anda dapat merujuk ke bagian 3.1.3.2.6 (CPSettingPage).
Parameter awal untuk membuka halaman SettingViewController adalah sebagai berikut:
uiComponentParam.params adalah sebagai berikut:
miniAppId: ID miniapp
userId: userId
miniAppUrl: URL halaman saat ini dari miniapp
miniAppName: appName
miniAppIcon: iconUrl
miniAppSlogan: slogan
miniAppDescription: description
miniAppVersion: version
Tombol "copy" digunakan untuk menyalin URL halaman saat ini:
Tombol "share": Jika fungsi “share” diperlukan, harap implementasikan protokol "EMASMiniAppMoreService". Kode contoh sebagai berikut:
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)
}
}Tombol "refresh" digunakan untuk me-restart miniapp.
Harap implementasikan protokol "EMASMiniAppMoreService" jika MiniApp saat ini berada di jendela lain.
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
}
}
}
}
}
Jika Anda ingin membuka halaman "about us" dan "scope setting" di halaman more kustom, kode contoh sebagai berikut:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
NSString *appId = self.miniAppInfo[@"miniAppId"] ?: @"";
NSString * code = @"";
switch (item.key) {
//收藏
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;
//设置
case MenuItemKeySettings:
{
//跳转设置页面
code = EMAS_OPEN_SETTING;
if (self.completionBlock && code && code .length != 0) {
self.completionBlock(@{@"code":code});
}
}
break;
//复制链接
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;
//分享
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;
//关于我们
case MenuItemKeyAboutUs:
{
code = EMAS_OPEN_ABOUT;
if (self.completionBlock && code && code .length != 0) {
self.completionBlock(@{@"code":code});
}
}
break;
//刷新
case MenuItemKeyRefresh:
{
code = EMAS_RESTART_MINIAPP;
if (self.completionBlock && code && code .length != 0) {
self.completionBlock(@{@"code":code});
}
}
default:
break;
}
}d. Di user AboutUsDialogfragment
uiComponentParam.params adalah sebagai berikut:
miniAppId: ID miniapp
userId: userId
miniAppUrl: URL halaman saat ini program mini
miniAppName: appName
miniAppIcon: iconUrl
miniAppSlogan: slogan
miniAppDescription: description
miniAppVersion: version.
e. Di user FavoriteDialogFragment
uiComponentParam.params adalah sebagai berikut:
miniAppId: ID miniapp
userId: userId
miniAppUrl: URL halaman saat ini dari miniapp
miniAppName: appName
miniAppIcon: iconUrl
miniAppSlogan: slogan
miniAppDescription: description
miniAppVersion: version
3.1.3 Implementasikan antarmuka melalui plugin
3.1.3.1 Implementasi Android
1. buat UIPlugin kustom client
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) {
/* Ini adalah data yang kami paket
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();
//Di sini Anda dapat menerapkan logika otorisasi Anda untuk memverifikasi kapan jendela pop-up diperlukan. Jika tidak diperlukan jendela pop-up,
//Anda dapat mengembalikan nilai 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, "Kesalahan: Izin tidak diketahui " + 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) {
//Data ini dapat berupa data yang dipilih pengguna untuk mengizinkan. Tentu saja, Anda juga dapat memilih untuk menyimpan pemrosesan logika saat tombol OK diklik,
//atau mengembalikan data tersebut untuk diproses.
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:Ini adalah data yang kami butuhkan dari Anda untuk mengonfirmasi keberhasilan otorisasi.
// 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, "Pengguna menolak permintaan otorisasi", 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("Kesalahan: Izin tidak diketahui " + 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("Pengguna menolak permintaan otorisasi", UIResultCodeConstants.ERROR));
}
}
}, uiComponentParam.getContext());
}
@Override
public Fragment createErrorPageFragment(UIComponentParam uiComponentParam) {
/* Ini adalah data yang kami paket
* HashMap<String, Object> map = new HashMap<>();
* map.put("errorCode", 1);
*/
return ErrorFragment.newInstance(uiComponentParam);
}
...
}2. Implementasi 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;
//Jika Anda adalah developer Android native dan menggunakan jendela pop-up,
//Anda dapat extends DialogFragment.
//Jika Anda adalah developer flutter,
//Anda dapat 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
}
//Ini adalah informasi miniinfo yang kami teruskan.
// Anda dapat mengurai sesuai dengan kunci yang kami teruskan.
// 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);
//Di sini Anda dapat melakukan operasi logika Anda dan mengembalikan data kembali
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. Implementasi 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. Implementasi SplashPageFragment
//Jika Anda ingin menyesuaikan layar splash saat applet dibuka,
//Anda dapat merujuk ke
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) {
//Di sini kami dapat mengembalikan kepada Anda kemajuan pengunduhan program mini
binding.progressBar.setProgress(progress);
binding.statusText.setText(progress+"%" );
}
@Override
public void setMiniAppInfo(HashMap<String, Object> params) {
//Di sini Anda dapat memperbarui antarmuka Anda untuk menampilkan informasi program mini jika diperlukan
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. Implementasi 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. Implementasi 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. Implementasi 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. Implementasi 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 Implementasi iOS
1. buat UIPlugin kustom client
#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"];
//scope yang sudah diotorisasi
NSMutableDictionary *authedScopeDic = [[NSMutableDictionary alloc] init];
//scope yang perlu diotorisasi (menyaring scope yang tidak dikenal)
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;
}
@end2. Implementasi 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;
//informasi miniapp
@property (nonatomic, strong) NSDictionary *miniAppInfo;
//appid miniapp
@property (nonatomic, strong) NSString *appId;
//userid
@property (nonatomic, strong)NSString *userId;
//memperbarui tata letak tinggi tableview
@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];
// Lakukan setup tambahan setelah memuat tampilan.
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];
}
}
//tidak ada nilai yang perlu diotorisasi sementara
if (array.count == 0) {
//informasi teks dan tampilan--logika pemrosesan
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 {
// Tinggi tabel default diatur menjadi 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. Implementasi 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];
// Lakukan setup tambahan setelah memuat tampilan.
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;
}
//sesuaikan constraint berdasarkan kondisi
[self updateConstraints];
}
-(void)setupErrorView{
// 1. Buat tampilan kontainer
UIView* container = [UIView new];
container.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:container];
// 2. Buat gambar kesalahan dan tambahkan ke kontainer
UIImageView *errorImage = [[UIImageView alloc] initWithImage:[CPCommonTool getImageByName:@"app_load_failure"]];
errorImage.contentMode = UIViewContentModeScaleAspectFit;
errorImage.translatesAutoresizingMaskIntoConstraints = NO;
self.mainImg = errorImage;
[container addSubview:errorImage]; // Tambahkan ke kontainer
// 3. Buat label petunjuk kesalahan dan tambahkan ke kontainer
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]; // Tambahkan ke kontainer
// constraint label kode kesalahan
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];
// Buat tombol refresh
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];
// Buat tombol keluar
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. Atur constraint
[NSLayoutConstraint activateConstraints:@[
// constraint kontainer
[container.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[container.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor constant:-hPix(100)],
[container.widthAnchor constraintEqualToAnchor:self.view.widthAnchor multiplier:0.8],
// constraint gambar kesalahan
[errorImage.topAnchor constraintEqualToAnchor:container.topAnchor],
[errorImage.centerXAnchor constraintEqualToAnchor:container.centerXAnchor],
[errorImage.widthAnchor constraintEqualToConstant:wPix(120)],
[errorImage.heightAnchor constraintEqualToConstant:wPix(120)],
// constraint label kesalahan
[errorLabel.topAnchor constraintEqualToAnchor:errorImage.bottomAnchor constant:hPix(16)],
[errorLabel.leadingAnchor constraintEqualToAnchor:container.leadingAnchor],
[errorLabel.trailingAnchor constraintEqualToAnchor:container.trailingAnchor],
// constraint label kode kesalahan
[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 {
// Implementasi logika refresh
if (self.complete) {
self.complete(1);
}
}
- (void)exitAction {
// Implementasi logika keluar
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 exception
case EMAS_ERROR_CODE_NETWORK_RESPONSE_EMPTY://network exception
case EMAS_ERROR_CODE_NETWORK_RESPONSE_SUCCESS_FALSE://network exception
case EMAS_ERROR_CODE_URL_VERSION_EMPTY://network exception
{
[cfn setErrorType:(CPErrorTypeConnectionError) errorCode:code];
}
break;
case EMAS_ERROR_CODE_NETWORK_RESPONSE_FORMAT_ERROR://cannot open this page
{
[cfn setErrorType:(CPErrorTypeOpenError) errorCode:code];
}
break;
case EMAS_ERROR_CODE_MINI_APP_DOWNLOAD_ERROR://miniapp load failed
case EMAS_ERROR_CODE_MINI_APP_UNZIP_ERROR://miniapp load failed
case EMAS_ERROR_CODE_MINI_APP_CACHE_ERROR://miniapp load failed
case EMAS_ERROR_CODE_MINI_APP_TYPE_NOT_SUPPORT://load failed
case EMAS_ERROR_CODE_WINDVANE_NOT_USED://load failed
case EMAS_ERROR_CODE_UNIAPP_NOT_USED://miniapp load failed
{
[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. Implementasi SplashPageViewController:
Untuk mendapatkan kemajuan pemuatan dan informasi miniapp, Anda perlu mengimplementasikan protokol 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];
// Lakukan setup tambahan setelah memuat tampilan.
[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];
// Buat tampilan kontainer
UIView *loadContainer = [UIView new];
loadContainer.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:loadContainer];
// Tambahkan gambar
UIImageView *imageView = [[UIImageView alloc] initWithImage:[CPCommonTool getImageByName:(@"miniapp_default_icon")]];
self.appIcon = imageView;
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.translatesAutoresizingMaskIntoConstraints = NO;
[loadContainer addSubview:imageView];
// Tambahkan label nama aplikasi
UILabel *label = self.titleLabel;
label.text = @"Miniapp Demo";
label.font = kFontWithWeight(16,500);
label.textAlignment = NSTextAlignmentCenter;
label.translatesAutoresizingMaskIntoConstraints = NO;
[loadContainer addSubview:label];
// Tambahkan progress bar
self.progressView.progress = 0;
self.progressView.translatesAutoresizingMaskIntoConstraints = NO;
self.progressView.tintColor = kColorWithHex(CP_THEME_COLOR);
[loadContainer addSubview:self.progressView];
// Tambahkan label persentase
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 = @[
// constraint kontainer
[loadContainer.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[loadContainer.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor constant:-64],
[loadContainer.widthAnchor constraintEqualToAnchor:self.view.widthAnchor multiplier:1],
// constraint gambar
[imageView.topAnchor constraintEqualToAnchor:loadContainer.topAnchor],
[imageView.centerXAnchor constraintEqualToAnchor:loadContainer.centerXAnchor],
[imageView.widthAnchor constraintEqualToConstant:wPix(56)],
[imageView.heightAnchor constraintEqualToConstant:wPix(56)],
// constraint label nama aplikasi
[label.topAnchor constraintEqualToAnchor:imageView.bottomAnchor constant:hPix(20)],
[label.leadingAnchor constraintEqualToAnchor:loadContainer.leadingAnchor],
[label.trailingAnchor constraintEqualToAnchor:loadContainer.trailingAnchor],
// constraint progress bar
[self.progressView.topAnchor constraintEqualToAnchor:label.bottomAnchor constant:hPix(22)],
[self.progressView.centerXAnchor constraintEqualToAnchor:loadContainer.centerXAnchor],
[self.progressView.widthAnchor constraintEqualToConstant:wPix(160)],
// constraint label persentase
[self.percentLabel.topAnchor constraintEqualToAnchor:self.progressView.bottomAnchor constant:hPix(8)],
[self.percentLabel.centerXAnchor constraintEqualToAnchor:self.progressView.centerXAnchor],
// constraint bawah kontainer
[loadContainer.bottomAnchor constraintEqualToAnchor:self.progressView.bottomAnchor constant:0]
];
// Atur constraint
[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];
});
}
@end5. Implementasi 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);
// Tampilan kontainer
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];
// Bilah judul
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];
// Tombol batal
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];
// constraint elemen bilah judul
iconImageView.translatesAutoresizingMaskIntoConstraints = NO;
titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
// constraint ikon
[iconImageView.leadingAnchor constraintEqualToAnchor:topView.leadingAnchor constant:wPix(24)],
[iconImageView.topAnchor constraintEqualToAnchor:topView.topAnchor constant:hPix(16)],
[iconImageView.widthAnchor constraintEqualToConstant:kIconSize],
[iconImageView.heightAnchor constraintEqualToConstant:kIconSize],
// constraint judul
[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 ],
// constraint tampilan kontainer
[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],
// constraint CollectionView
[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],
// constraint tombol batal
[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];
// ikon
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];
// judul
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];
// constraint
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)],
// constraint ikon
[iconView.centerYAnchor constraintEqualToAnchor:c.centerYAnchor],
[iconView.centerXAnchor constraintEqualToAnchor:c.centerXAnchor],
[iconView.widthAnchor constraintEqualToConstant:wPix(32)],
[iconView.heightAnchor constraintEqualToConstant:wPix(32)],
// constraint judul
[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]
]];
// gaya latar belakang
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;
}
//status tidak tersedia
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) {
//收藏
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;
//设置
case MenuItemKeySettings:
{
//跳转设置页面
code = EMAS_OPEN_SETTING;
}
break;
//复制链接
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;
//分享
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;
//关于我们
case MenuItemKeyAboutUs:
{
code = EMAS_OPEN_ABOUT;
}
break;
//刷新
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];
//kompatibel dengan iPad
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;
}
@end6. Implementasi 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];
// Lakukan setup tambahan setelah memuat tampilan.
[self setupData];
[self setupTableView];
}
- (void)miniAppInfo:(NSDictionary *)info {
self.miniAppInfoDict = info;
}
- (void)setupData {
// Inisialisasi sumber data
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 {
//tampilan header
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;
}
// Buat tampilan kontainer
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";
// Buat label teks
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;
}
}
@end7. Implementasi 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];
// Paksa pembaruan tata letak
self.bottomConstraint.constant = 0;
// Jalankan animasi tampilan
[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];
// Tombol tanda seru
_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;
// Tata letak ikon dan nama aplikasi
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];
// Kontainer konten (gunakan UIStackView)
UIStackView *contentStack = [[UIStackView alloc] init];
contentStack.axis = UILayoutConstraintAxisVertical;
contentStack.spacing = hPix(16);
contentStack.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:contentStack];
// Tambahkan elemen konten ke stackView
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;
// Kontainer tombol
UIView *buttonContainer = [UIView new];
buttonContainer.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:buttonContainer];
// Tombol tolak
UIButton *rejectBtn = [CPCommonTool getThemeLightButton];
[rejectBtn setTitle:kLocalString(@"mini_app_cancel") forState:UIControlStateNormal];
[rejectBtn addTarget:self action:@selector(rejectAction) forControlEvents:UIControlEventTouchUpInside];
[buttonContainer addSubview:rejectBtn];
// Tombol izinkan
UIButton *allowBtn = [CPCommonTool getThemeDarkButton];
[allowBtn setTitle:kLocalString(@"mini_app_ok") forState:UIControlStateNormal];
[allowBtn addTarget:self action:@selector(allowAction) forControlEvents:UIControlEventTouchUpInside];
[buttonContainer addSubview:allowBtn];
// Konfigurasi constraint
[NSLayoutConstraint activateConstraints:@[
// Tombol tanda seru
[_infoButton.topAnchor constraintEqualToAnchor:container.topAnchor constant:16],
[_infoButton.trailingAnchor constraintEqualToAnchor:container.trailingAnchor constant:-wPix(24)],
[_infoButton.widthAnchor constraintEqualToConstant:24],
[_infoButton.heightAnchor constraintEqualToConstant:24],
// Ikon dan nama aplikasi
[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)],
// Area konten
[contentStack.topAnchor constraintEqualToAnchor:icon.bottomAnchor constant:hPix(32)],
[contentStack.leadingAnchor constraintEqualToAnchor:container.leadingAnchor constant:wPix(24)],
[contentStack.trailingAnchor constraintEqualToAnchor:container.trailingAnchor constant:-wPix(24)],
// Kontainer tombol
[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],
// Tata letak tombol
[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)],
]];
// Perhitungan tinggi dinamis
[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;
// Buat lapisan masker
_maskView = self.maskView;
_maskView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
//_maskView.alpha = 0;
[superView insertSubview:_maskView belowSubview:self.container];
// Dapatkan tinggi dinamis
[self.view layoutIfNeeded];
// Atur constraint (posisi awal di bawah layar)
_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 -Lihat informasi miniapp
- (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;
}
@end8. Implementasi 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];
// Tampilan Kontainer
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];
// Label Judul
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];
// Tombol Kembali
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];
// Stack Konten
self.contentStackView = [[UIStackView alloc] init];
self.contentStackView.axis = UILayoutConstraintAxisVertical;
self.contentStackView.spacing = hPix(16);
self.contentStackView.translatesAutoresizingMaskIntoConstraints = NO;
[self.containerView addSubview:self.contentStackView];
// Constraint
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 Daftarkan plugin
3.1.4.1 Di Android
private void initWindVaneMiniApp() {
// 1. konfigurasi parameter MiniAppService
MiniAppInitConfig config = new MiniAppInitConfig.Builder()
.setUseWindVane(true)
.setUseUniApp(false)
.setHost("poc.superapp-intl.com")
.setAppCode("YourAppCode")
.setAccessKey("YourAccessKey")
.setSecretKey("YourSecretKey")
.build();
// 2. buat objek miniAppService dan inisialisasi
IMiniAppService miniAppService = new MiniAppService();
miniAppService.initialize(application, config);
...
// 2. daftarkan plugin
miniAppService.registerPlugin("CustomUIPlugin", CustomUIPlugin(this));
}3.1.4.2 Di iOS
private func configMiniAppContainer() {
// 1. konfigurasi parameter MiniAppService
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. buat objek miniAppService dan inisialisasi
let miniAppService = EMASMiniAppServiceImpl();
miniAppService.initialize(miniAppInitConfig)
EMASServiceManager.sharedInstance().registerServiceProtocol("EMASMiniAppService", impClass: "EMASMiniAppServiceImpl", target: miniAppService)
// 3. daftarkan plugin
let uiPlugin = EMASClientCustomUIPlugin()
miniAppService.registerPlugin("ClientCustomUIPlugin", plugin: uiPlugin)
}3.2 Skenario Flutter
3.2.0 Bagan alir Interaksi Flutter

3.2.1 Impor paket terkait
// 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 Implementasikan FlutterAuthDialogFragment
Untuk klien yang ingin menggunakan halaman Flutter untuk menampilkan kotak pop-up, mereka perlu mengimplementasikan kontainer FlutterFragment terlebih dahulu.
public class FlutterAuthDialogFragment extends FlutterFragment {
private static final String TAG = FlutterAuthDialogFragment.class.getName();
// TODO: Ganti nama parameter argumen, pilih nama yang sesuai
// dengan parameter inisialisasi fragment, misalnya ARG_ITEM_NUMBER
private static final String ARG_ROUTE = "route";
private static final String ARG_PARAM2 = "param2";
// TODO: Ganti nama dan ubah jenis parameter
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 belum diinisialisasi");
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");
// Tangani tindakan otorisasi persetujuan
authService.saveAuth(FlutterAuthDialogFragment.this.getActivity(), permission, userId, miniAppId);
// kembalikan 'result' ke kode flutter
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");
// Tangani tindakan pemeriksaan otorisasi
boolean isAuthorized = authService.isAuthorized(FlutterAuthDialogFragment.this.getActivity(), permission, userId, miniAppId);
// kembalikan 'result' ke kode flutter
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 Implementasikan halaman Auth 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",
};
// langkah 7: pengguna mengajukan/menolak otorisasi
WindVaneMiniAppManager.authChannel.invokeMethod(approvalItem[index] ? "flutter_revoke_auth_action" : "flutter_approval_auth_action", args).then((result) {
// langkah 9: menerima hasil otorisasi
approvalItem[index] = result;
// langkah 10: muat ulang halaman
setState(() {});
});
},
);
},
),
);
}
}
3.2.4 Implementasikan channel metode flutter
class WindVaneMiniAppManager {
static const MethodChannel authChannel = const MethodChannel("flutter_auth_dialog");
}3.2.5 Deklarasikan rute halaman 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;
// langkah 1: daftarkan rute halaman auth flutter
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 Implementasikan antarmuka melalui plugin
public class FlutterUIPlugin extends BasePlugin implements UIExtension {
public FlutterUIPlugin(Context context) {
super(context);
}
// langkah 2: UIService memanggil createUserDeviceInfoAuthDialogFragment(...) untuk membuat fragment Dialog Auth
@Override
public Fragment createUserDeviceInfoAuthDialogFragment(HashMap<String, Object> params) {
// "/ui/auth" adalah rute halaman flutter
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 Daftarkan plugin
private void initWindVaneMiniApp() {
// 1. konfigurasi parameter MiniAppService
MiniAppInitConfig config = new MiniAppInitConfig.Builder()
.setUseWindVane(true)
.setUseUniApp(false)
.setHost("poc.superapp-intl.com")
.setAppCode("YourAppCode")
.setAccessKey("YourAccessKey")
.setSecretKey("YourSecretKey")
.build();
// 2. buat objek miniAppService dan inisialisasi
IMiniAppService miniAppService = new MiniAppService();
miniAppService.initialize(application, config);
...
// 2. daftarkan plugin
miniAppService.registerPlugin("FlutterUIPlugin", FlutterUIPlugin(this));
}iOS
private func configMiniAppContainer() {
// 1. konfigurasi parameter MiniAppService
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. buat objek miniAppService dan inisialisasi
let miniAppService = EMASMiniAppServiceImpl();
miniAppService.initialize(miniAppInitConfig)
EMASServiceManager.sharedInstance().registerServiceProtocol("EMASMiniAppService", impClass: "EMASMiniAppServiceImpl", target: miniAppService)
// 3. daftarkan plugin
let aliUIPlugin = EMASAliCustomUIPlugin()
miniAppService.registerPlugin("EMASAliCustomUIPlugin", plugin: aliUIPlugin)
}3.3 Halaman semi transparan Flutter
3.3.1 Halaman semi transparan
|
3.3.2 Di 'main.dart', pastikan latar belakang MaterialApp transparan:
void main() {
runApp(
MaterialApp(
// atur latar belakang transparan
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 Demo halaman flutter semi transparan
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(
// atur nilai semi transparan
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",
};
// langkah 7: pengguna mengajukan/menolak otorisasi
WindVaneMiniAppManager.authChannel.invokeMethod(approvalItem[index] ? "flutter_revoke_auth_action" : "flutter_approval_auth_action", args).then((result) {
// langkah 9: menerima hasil otorisasi
approvalItem[index] = result;
// langkah 10: muat ulang halaman
setState(() {});
});
},
);
},
),
),
],
),
],
);
}
}3.3.4 Buat FlutterFragment dan atur transparansi
3.3.4.1 Di 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 Di iOS
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.init(white: 0.0, alpha: 0.5)
//flutter_auth_dialog
}3.3.5 Untuk konten lebih detail
silakan merujuk ke branch "feature/flutter_page_in_native" di proyek flutter_windvane_demo.
4 Antarmuka Umum
4.1 Antarmuka Auth
4.1.1 Di Android
4.1.1.1 Antarmuka Auth dalam 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 Gunakan AuthService
IAuthService authService = PluginEnv.getInstance().getContainerContext().getAuthService();
authService.saveAuth(context, scope, userId, miniAppId);
4.1.2 Di iOS
4.1.2.1 Antarmuka Auth dalam 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;
@end4.1.2.2 Gunakan AuthService
EMASPluginEnv.shareInstance().getContainerContext().getAuthService().saveAuthList!(scopes, userId: userId, miniAppId: miniAppId)
4.2 Antarmuka UI
4.2.1 Di Android
4.2.1.1 Antarmuka UI dalam SDK
public interface UIExtension {
// Digunakan untuk membuat dialog 'More', lihat ‘gambar 1’ dalam catatan untuk contoh gaya
public default Fragment createMoreDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Digunakan untuk membuat dialog 'Favorite', lihat ‘gambar 2’ dalam catatan untuk contoh gaya
public default Fragment createFavoriteDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Digunakan untuk membuat dialog 'Auth', lihat ‘gambar 3’ dalam catatan untuk contoh gaya
public default Fragment createUserDeviceInfoAuthDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Digunakan untuk membuat halaman anak 'Error', lihat ‘gambar 5’ dalam catatan untuk contoh gaya
public default Fragment createErrorPageFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Digunakan untuk membuat halaman anak 'Slash', lihat ‘gambar 6’ dalam catatan untuk contoh gaya
public default Fragment createSplashPageFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Digunakan untuk membuat dialog 'About US', lihat ‘gambar 8’ dalam catatan untuk contoh gaya
public default Fragment createAboutUsDialogFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
// Digunakan untuk membuat halaman anak 'User Info Auth', lihat ‘gambar 7’ dalam catatan untuk contoh gaya
public default Fragment createUserInfoAuthPageFragment(UIComponentParam uiComponentParam) {
return NoExistFragment.newInstance();
}
}public class UIComponentParam {
private UIResultCallBack uiResultCallBack;
private Context context;
private HashMap<String, Object> params;
/**
* uiResultCallBack: Data yang dikembalikan
* data: Data yang dikirim
* **/
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 Di iOS
@protocol EMASIUIExtension <NSObject>
@optional
// Digunakan untuk membuat dialog 'More', lihat ‘figure 1’ pada catatan untuk contoh gaya
- (nullable UIViewController *)createMoreDialogViewController:(EMASUIComponentParam *)uiComponentParam;
// Digunakan untuk membuat dialog 'Favorite', lihat ‘figure 2’ pada catatan untuk contoh gaya
- (nullable UIViewController *)createFavoriteDialogViewController:(EMASUIComponentParam *)uiComponentParam;
// Digunakan untuk membuat dialog 'Auth', lihat ‘figure 3’ pada catatan untuk contoh gaya
- (nullable UIViewController *)createUserDeviceInfoAuthDialogViewController:(EMASUIComponentParam *)uiComponentParam;
// Digunakan untuk membuat sub halaman 'Error', lihat ‘figure 5’ pada catatan untuk contoh gaya
- (nullable UIViewController *)createErrorPageViewController:(EMASUIComponentParam *)uiComponentParam;
// Digunakan untuk membuat sub halaman 'Slash', lihat ‘figure 6’ pada catatan untuk contoh gaya
- (nullable UIViewController *)createSplashPageViewController:(EMASUIComponentParam *)uiComponentParam;
// Digunakan untuk membuat dialog 'About US', lihat ‘figure 8’ pada catatan untuk contoh gaya
- (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);
@endFOUNDATION_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 UI Default Baru dalam SDK
figure 1 more dialog |
|
figure 2 favorite dialog |
|
figure 3 auth dialog |
|
figure 4 loading dialog |
|
figure 5 error page |
|
figure 6 splash page |
|
figure 7 user info auth page |
|
figure 8 about us dialog |
|
|








