native webview

This commit is contained in:
mohamadmahdi jebeli 2025-06-02 15:18:20 +03:30
parent 126f499bd5
commit 898794e1c3
8 changed files with 235 additions and 16 deletions

View File

@ -113,4 +113,5 @@ dependencies {
implementation "androidx.sqlite:sqlite-framework:2.1.0"
implementation "androidx.sqlite:sqlite:2.1.0"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
implementation 'androidx.appcompat:appcompat:1.6.1'
}

View File

@ -74,6 +74,11 @@
</provider>
<!-- End FlutterDownloader customization -->
<activity android:name=".WebActivity" />
<activity
android:name=".FullscreenWebViewActivity"
android:theme="@style/FullscreenWebViewTheme" />
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"

View File

@ -0,0 +1,104 @@
package com.didvan.didvanapp
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.webkit.PermissionRequest
import android.webkit.WebChromeClient
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
class FullscreenWebViewActivity : AppCompatActivity() {
private lateinit var webView: WebView
private val PERMISSION_REQUEST_CODE = 1234
private var pendingPermissionRequest: PermissionRequest? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
webView = WebView(this)
setContentView(webView)
val url = intent.getStringExtra("url") ?: "https://www.google.com"
val settings = webView.settings
settings.javaScriptEnabled = true
settings.mediaPlaybackRequiresUserGesture = false
settings.allowContentAccess = true
settings.allowFileAccess = true
settings.domStorageEnabled = true
settings.databaseEnabled = true
settings.cacheMode = WebSettings.LOAD_DEFAULT
webView.webViewClient = WebViewClient()
webView.webChromeClient = object : WebChromeClient() {
override fun onPermissionRequest(request: PermissionRequest?) {
runOnUiThread {
if (request == null) {
super.onPermissionRequest(request)
return@runOnUiThread
}
val requestedResources = request.resources
val permissionsNeeded = mutableListOf<String>()
for (resource in requestedResources) {
when (resource) {
PermissionRequest.RESOURCE_VIDEO_CAPTURE -> {
if (ContextCompat.checkSelfPermission(this@FullscreenWebViewActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.CAMERA)
}
}
PermissionRequest.RESOURCE_AUDIO_CAPTURE -> {
if (ContextCompat.checkSelfPermission(this@FullscreenWebViewActivity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.RECORD_AUDIO)
}
}
}
}
if (permissionsNeeded.isNotEmpty()) {
// درخواست پرمیژن runtime
pendingPermissionRequest = request
ActivityCompat.requestPermissions(this@FullscreenWebViewActivity, permissionsNeeded.toTypedArray(), PERMISSION_REQUEST_CODE)
} else {
// پرمیژن ها داده شده، قبول کن درخواست رو
request.grant(request.resources)
}
}
}
}
webView.loadUrl(url)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode == PERMISSION_REQUEST_CODE) {
var allGranted = true
for (result in grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allGranted = false
break
}
}
if (allGranted) {
pendingPermissionRequest?.grant(pendingPermissionRequest?.resources)
} else {
pendingPermissionRequest?.deny()
}
pendingPermissionRequest = null
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
override fun onDestroy() {
super.onDestroy()
webView.destroy()
}
}

View File

@ -1,20 +1,87 @@
package com.didvan.didvanapp
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.PersistableBundle
import android.provider.MediaStore
import android.util.Log
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugin.common.MethodChannel
import java.io.File
import java.io.FileInputStream
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.didvan.shareFile"
@Override
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"copyToDownloads" -> {
val filePath = call.argument<String>("filePath") ?: return@setMethodCallHandler
val fileName = call.argument<String>("fileName") ?: return@setMethodCallHandler
val success = copyFileToDownloads(filePath, fileName)
if (success) {
result.success("File copied successfully")
} else {
result.error("FILE_COPY_ERROR", "Failed to copy file", null)
}
}
"openWebView" -> {
val url = call.argument<String>("url") ?: return@setMethodCallHandler
openWebView(url)
result.success(null)
}
else -> result.notImplemented()
}
}
}
private fun copyFileToDownloads(filePath: String, fileName: String): Boolean {
return try {
val context: Context = applicationContext
val file = File(filePath)
if (!file.exists()) return false
val resolver = context.contentResolver
val contentValues = ContentValues().apply {
put(MediaStore.Downloads.DISPLAY_NAME, fileName)
put(MediaStore.Downloads.MIME_TYPE, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
put(MediaStore.Downloads.IS_PENDING, 1)
}
val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
?: return false
resolver.openOutputStream(uri).use { outputStream ->
FileInputStream(file).use { inputStream ->
inputStream.copyTo(outputStream!!)
}
}
contentValues.clear()
contentValues.put(MediaStore.Downloads.IS_PENDING, 0)
resolver.update(uri, contentValues, null, null)
true
} catch (e: Exception) {
Log.e("FileCopy", "Error copying file to downloads", e)
false
}
}
private fun openWebView(url: String) {
Log.d("MainActivity", "Opening WebView with URL: $url")
val intent = Intent(this, FullscreenWebViewActivity::class.java)
intent.putExtra("url", url)
startActivity(intent)
}
}

View File

@ -1,17 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
@ -25,4 +17,9 @@
<item name="android:background">?android:attr/colorBackground</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<!-- ✅ نسخه صحیح -->
<style name="FullscreenWebViewTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowFullscreen">true</item>
</style>
</resources>

13
lib/services/webview.dart Normal file
View File

@ -0,0 +1,13 @@
import 'package:flutter/services.dart';
class NativeWebViewLauncher {
static const MethodChannel _channel = MethodChannel('com.didvan.shareFile');
static Future<void> openWebView(String url) async {
try {
await _channel.invokeMethod('openWebView', {'url': url});
} on PlatformException catch (e) {
print("Failed to open native webview: '${e.message}'.");
}
}
}

View File

@ -3,6 +3,7 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/views/home/main/widgets/banner.dart';
import 'package:didvan/views/home/widgets/categories.dart';
import 'package:didvan/views/widgets/ai_banner.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/material.dart';

View File

@ -0,0 +1,31 @@
import 'package:didvan/services/webview.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/svg.dart';
class AiBanner extends StatelessWidget {
const AiBanner({
super.key,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
NativeWebViewLauncher.openWebView(
'https://www.aisada.ir/app/page1.html');
},
child: Padding(
padding: const EdgeInsets.only(top: 20),
child: Stack(
children: [
Icon(Icons.insert_comment_sharp),
Positioned(
left: 15,
child: Icon(Icons.import_contacts))
],
),
),
);
}
}