feat: 添加应用品牌信息支持,重构主页 URL 生成逻辑
This commit is contained in:
@@ -3,8 +3,13 @@ package io.openim.flutter.openim
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.core.content.FileProvider
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
@@ -12,10 +17,12 @@ import org.conscrypt.Conscrypt
|
||||
import java.security.Security
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
|
||||
class MainActivity : FlutterActivity() {
|
||||
private val CHANNEL = "io.openim.flutter.openim/apk_info"
|
||||
private val SHELL_BRANDING_CHANNEL = "io.openim.flutter.im_webview_app/shell_branding"
|
||||
private val TAG = "MainActivity"
|
||||
|
||||
override fun onCreate(savedInstanceState: android.os.Bundle?) {
|
||||
@@ -80,6 +87,53 @@ class MainActivity : FlutterActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SHELL_BRANDING_CHANNEL).setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"getShellBranding" -> {
|
||||
try {
|
||||
result.success(
|
||||
mapOf(
|
||||
"appName" to getCurrentAppName(),
|
||||
"appLogo" to getCurrentAppIconDataUrl()
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
result.error("ERROR", "无法获取当前应用品牌信息: ${e.message}", null)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCurrentAppName(): String {
|
||||
val label = packageManager.getApplicationLabel(applicationInfo)
|
||||
return label?.toString() ?: ""
|
||||
}
|
||||
|
||||
private fun getCurrentAppIconDataUrl(): String {
|
||||
val icon = packageManager.getApplicationIcon(packageName)
|
||||
val bitmap = drawableToBitmap(icon)
|
||||
val output = ByteArrayOutputStream()
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, output)
|
||||
val base64 = Base64.encodeToString(output.toByteArray(), Base64.NO_WRAP)
|
||||
return "data:image/png;base64,$base64"
|
||||
}
|
||||
|
||||
private fun drawableToBitmap(drawable: Drawable): Bitmap {
|
||||
if (drawable is BitmapDrawable && drawable.bitmap != null) {
|
||||
return drawable.bitmap
|
||||
}
|
||||
|
||||
val width = if (drawable.intrinsicWidth > 0) drawable.intrinsicWidth else 192
|
||||
val height = if (drawable.intrinsicHeight > 0) drawable.intrinsicHeight else 192
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||
drawable.draw(canvas)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
private fun getApkPackageName(apkPath: String): String? {
|
||||
|
||||
@@ -4,6 +4,8 @@ enum Environment {
|
||||
|
||||
class AppConfig {
|
||||
static const Environment currentEnvironment = Environment.production;
|
||||
static const String appName = '本地打包';
|
||||
static const String appLogo = '';
|
||||
|
||||
static final Map<Environment, List<String>> _environmentHosts = {
|
||||
Environment.production: [
|
||||
@@ -15,11 +17,15 @@ class AppConfig {
|
||||
return _environmentHosts[currentEnvironment] ?? const [];
|
||||
}
|
||||
|
||||
static String get homeUrl {
|
||||
static String homeUrl({String? appName, String? appLogo}) {
|
||||
final host = environmentHosts.isNotEmpty
|
||||
? environmentHosts.first
|
||||
: 'h5-im.imharry.work';
|
||||
return _normalizeHomeUrl(host);
|
||||
return _withShellBranding(
|
||||
_normalizeHomeUrl(host),
|
||||
appName: appName,
|
||||
appLogo: appLogo,
|
||||
);
|
||||
}
|
||||
|
||||
static String _normalizeHomeUrl(String host) {
|
||||
@@ -29,4 +35,26 @@ class AppConfig {
|
||||
}
|
||||
return 'https://$value/';
|
||||
}
|
||||
|
||||
static String _withShellBranding(
|
||||
String url, {
|
||||
String? appName,
|
||||
String? appLogo,
|
||||
}) {
|
||||
final uri = Uri.parse(url);
|
||||
final queryParameters = Map<String, String>.from(uri.queryParameters)
|
||||
..['flutter_shell'] = '1';
|
||||
|
||||
final trimmedName = (appName ?? AppConfig.appName).trim();
|
||||
if (trimmedName.isNotEmpty) {
|
||||
queryParameters['shell_app_name'] = trimmedName;
|
||||
}
|
||||
|
||||
final trimmedLogo = (appLogo ?? AppConfig.appLogo).trim();
|
||||
if (trimmedLogo.isNotEmpty) {
|
||||
queryParameters['shell_app_logo'] = trimmedLogo;
|
||||
}
|
||||
|
||||
return uri.replace(queryParameters: queryParameters).toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,12 @@ import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
|
||||
|
||||
import 'config/app_config.dart';
|
||||
|
||||
final _homeUrl = AppConfig.homeUrl;
|
||||
const _shellBackground = Color(0xFFF8FBFF);
|
||||
const _shellAccent = Color(0xFF0089FF);
|
||||
const _shellSubText = Color(0xFF8E9AB0);
|
||||
const _resumeCoverDuration = Duration(milliseconds: 700);
|
||||
const _shellBrandingChannel =
|
||||
MethodChannel('io.openim.flutter.im_webview_app/shell_branding');
|
||||
const _stopWebMediaScript = r'''
|
||||
(() => {
|
||||
try {
|
||||
@@ -29,7 +30,7 @@ const _stopWebMediaScript = r'''
|
||||
})();
|
||||
''';
|
||||
|
||||
void main() {
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
const SystemUiOverlayStyle(
|
||||
@@ -39,29 +40,70 @@ void main() {
|
||||
systemNavigationBarIconBrightness: Brightness.dark,
|
||||
),
|
||||
);
|
||||
runApp(const ImWebViewApp());
|
||||
final shellBranding = await ShellBranding.load();
|
||||
runApp(ImWebViewApp(shellBranding: shellBranding));
|
||||
}
|
||||
|
||||
class ShellBranding {
|
||||
const ShellBranding({
|
||||
required this.appName,
|
||||
required this.appLogo,
|
||||
});
|
||||
|
||||
static const fallback = ShellBranding(
|
||||
appName: AppConfig.appName,
|
||||
appLogo: AppConfig.appLogo,
|
||||
);
|
||||
|
||||
final String appName;
|
||||
final String appLogo;
|
||||
|
||||
static Future<ShellBranding> load() async {
|
||||
try {
|
||||
final data = await _shellBrandingChannel
|
||||
.invokeMapMethod<String, String>('getShellBranding');
|
||||
final appName = _trim(data?['appName']);
|
||||
final appLogo = _trim(data?['appLogo']);
|
||||
|
||||
return ShellBranding(
|
||||
appName: appName.isNotEmpty ? appName : fallback.appName,
|
||||
appLogo: appLogo.isNotEmpty ? appLogo : fallback.appLogo,
|
||||
);
|
||||
} catch (_) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
static String _trim(String? value) => value?.trim() ?? '';
|
||||
}
|
||||
|
||||
class ImWebViewApp extends StatelessWidget {
|
||||
const ImWebViewApp({super.key});
|
||||
const ImWebViewApp({
|
||||
super.key,
|
||||
this.shellBranding = ShellBranding.fallback,
|
||||
});
|
||||
|
||||
final ShellBranding shellBranding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: '集中营',
|
||||
title: shellBranding.appName,
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1F6FEB)),
|
||||
scaffoldBackgroundColor: _shellBackground,
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const H5ShellPage(),
|
||||
home: H5ShellPage(shellBranding: shellBranding),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class H5ShellPage extends StatefulWidget {
|
||||
const H5ShellPage({super.key});
|
||||
const H5ShellPage({super.key, required this.shellBranding});
|
||||
|
||||
final ShellBranding shellBranding;
|
||||
|
||||
@override
|
||||
State<H5ShellPage> createState() => _H5ShellPageState();
|
||||
@@ -69,6 +111,7 @@ class H5ShellPage extends StatefulWidget {
|
||||
|
||||
class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
||||
late final WebViewController _controller;
|
||||
late final String _homeUrl;
|
||||
|
||||
int _progress = 0;
|
||||
String? _loadError;
|
||||
@@ -79,6 +122,10 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_homeUrl = AppConfig.homeUrl(
|
||||
appName: widget.shellBranding.appName,
|
||||
appLogo: widget.shellBranding.appLogo,
|
||||
);
|
||||
_controller = _buildController()..loadRequest(Uri.parse(_homeUrl));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ import 'package:im_webview_app/main.dart';
|
||||
|
||||
void main() {
|
||||
test('creates the WebView shell app widget', () {
|
||||
expect(const ImWebViewApp(), isA<Widget>());
|
||||
expect(
|
||||
const ImWebViewApp(shellBranding: ShellBranding.fallback),
|
||||
isA<Widget>(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user