How to set darkmode in webview using jetpack compose? - android-webview

I try to enable dark mode on webview, which is not working, and also setForceDark is deprecated.
I am looking solution to enable dark mode on web view using jetpack compose
package com.blogspot.boltuix
import android.annotation.SuppressLint
import android.content.res.Configuration
import android.os.Bundle
import android.view.ViewGroup
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.webkit.WebSettingsCompat
import androidx.webkit.WebViewFeature
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WebViewPage("https://www.boltuix.com/")
}
}
}
#SuppressLint("SetJavaScriptEnabled")
#Composable
fun WebViewPage(url: String){
val context = LocalContext.current
//The Configuration object represents all of the current configurations, not just the ones that have changed.
val configuration = LocalConfiguration.current
when (configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
Toast.makeText(context, "landscape", Toast.LENGTH_SHORT).show()
}
else -> {
Toast.makeText(context, "portrait", Toast.LENGTH_SHORT).show()
}
}
// Adding a WebView inside AndroidView
// with layout as full screen
AndroidView(factory = {
WebView(it).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
webViewClient = WebViewClient()
// to play video on a web view
settings.javaScriptEnabled = true
// to verify that the client requesting your web page is actually your Android app.
settings.userAgentString = System.getProperty("http.agent") //Dalvik/2.1.0 (Linux; U; Android 11; M2012K11I Build/RKQ1.201112.002)
// feature 1 : dark mode (auto system setup)
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
WebSettingsCompat.setForceDark(settings, WebSettingsCompat.FORCE_DARK_ON)
}
loadUrl(url)
}
}, update = {
it.loadUrl(url)
})
}
App-level Gradle file:
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.blogspot.boltuix'
compileSdk 33
defaultConfig {
applicationId "com.blogspot.boltuix"
minSdk 24
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.2.0-beta01'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.0'
implementation 'androidx.activity:activity-compose:1.5.0'
implementation "androidx.compose.ui:ui:1.3.0-alpha01"
implementation "androidx.compose.ui:ui-tooling-preview:1.3.0-alpha01"
implementation 'androidx.compose.material3:material3:1.0.0-alpha14'
testImplementation 'junit:junit:4.13.2'
implementation "androidx.webkit:webkit:1.5.0-beta01"
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.3.0-alpha01"
debugImplementation "androidx.compose.ui:ui-tooling:1.3.0-alpha01"
debugImplementation "androidx.compose.ui:ui-test-manifest:1.3.0-alpha01"
}
I tested OS 11 and 13 - WEB VIEW is working, but not changing dark mode.

This is what I did...
Added this dependency in build.gradle.
implementation "androidx.webkit:webkit:$webkitVersion"
Declare the object below. It is responsible to provide the correct Configuration object in according to the Dark Mode.
object ConfigurationUtil {
fun getConfiguration(context: Context): Configuration {
/**
* issue tracker : https://issuetracker.google.com/issues/170328697
* Web view resets uiMode (Day/Night) in Configuration
*/
val configuration = context.resources.configuration
val configurationNighMode = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
val appCompatNightMode = AppCompatDelegate.getDefaultNightMode()
val newUiModeConfiguration = when {
configurationNighMode == Configuration.UI_MODE_NIGHT_NO && appCompatNightMode == UiModeManager.MODE_NIGHT_YES -> {
Configuration.UI_MODE_NIGHT_YES or (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv())
}
configurationNighMode == Configuration.UI_MODE_NIGHT_YES && appCompatNightMode == UiModeManager.MODE_NIGHT_NO -> {
Configuration.UI_MODE_NIGHT_NO or (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv())
}
else -> null
}
if (newUiModeConfiguration != null) {
val fixedConfiguration = Configuration().apply {
configuration.uiMode = newUiModeConfiguration
}
context.createConfigurationContext(fixedConfiguration)
}
return configuration
}
}
Use the WebView inside of an AndroidView....
#Composable
fun WebViewScreen() {
AndroidView(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
factory = { context ->
object : WebView(context) {
init {
val assetLoader = WebViewAssetLoader.Builder()
.addPathHandler("/res/", WebViewAssetLoader.ResourcesPathHandler(context))
.build()
webChromeClient = WebChromeClient()
webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView,
request: WebResourceRequest
): Boolean {
return false
}
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
return assetLoader.shouldInterceptRequest(request.url)
}
}
setBackgroundColor(0)
val configuration = ConfigurationUtil.getConfiguration(context)
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
when (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES -> {
WebSettingsCompat.setForceDark(
settings,
WebSettingsCompat.FORCE_DARK_ON
)
}
Configuration.UI_MODE_NIGHT_NO, Configuration.UI_MODE_NIGHT_UNDEFINED -> {
WebSettingsCompat.setForceDark(
settings,
WebSettingsCompat.FORCE_DARK_OFF
)
}
else -> {
WebSettingsCompat.setForceDark(
settings,
WebSettingsCompat.FORCE_DARK_AUTO
)
}
}
}
settings.allowFileAccess = false
settings.allowContentAccess = false
setDownloadListener { url, _, _, _, _ ->
url?.let {
try {
context.startActivity(
Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(it)
}
)
} catch (e: Exception) {
Toast.makeText(context, "Error opening link", Toast.LENGTH_LONG)
.show()
}
}
}
loadDataWithBaseURL(
ResourceBaseUrl,
"This is a web view. Check if it is working on Dark Mode",
"text/html",
"UTF-8",
null
)
}
}
}
)
}

Related

Mixed Jetpack Compose / Activity app navigation: from compose to activity and back

I am a newbie in Android development so I might be missing something simple here.
I have a compose setup using androidx.navigation:navigation-compose:2.5.3 where I have the routing setup:
sealed class Routes(val route: String) {
object Login : Routes("Login")
object TermsOfService : Routes("TermsOfService")
object Home : Routes("Home")
}
#Composable
fun Routing(startDestination: String) {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = startDestination) {
composable(Routes.Login.route) {
LoginPage(navController = navController, LoginViewModel(AppEnvironment.sdk))
}
composable(Routes.TermsOfService.route) {
TermsOfService(navController = navController)
}
composable(Routes.Home.route) {
Home(navController = navController)
}
}
}
I would setup my MainActivity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
App_SenderTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
val isLoggedIn = AppEnvironment.encryptedSettingsRepo.isUserLoggedIn.get().toBoolean()
println("Routing based on is_user_logged_in $isLoggedIn")
if (isLoggedIn) {
Routing(startDestination = Routes.Home.route)
} else {
Routing(startDestination = Routes.Login.route)
}
}
}
}
}
}
The Home itself is an Activity setup like:
#Composable
fun Home(navController: NavHostController) {
Box(modifier = Modifier.fillMaxSize()) {
ScaffoldWithTopBarHome(navController)
}
}
#SuppressLint("UnusedMaterialScaffoldPaddingParameter")
#Composable
fun ScaffoldWithTopBarHome(navController: NavHostController) {
Scaffold(
topBar = {
CustomTopAppBar(navController, "Sender", true)
}, content = {
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
val context = LocalContext.current
context.startActivity(Intent(context, HomeActivity::class.java))
}
})
}
class HomeActivity : AppCompatActivity() {
fun logout() {
// HOW TO GO BACK TO COMPOSE IF I DON'T HAVE A COMPOSE NAVIGATION REFERENCE
}
}
I can easily navigate to the HomeActivity from any composable setup since I have the NavHostController, e.g. navController.navigate(Routes.Home.route), but how can I navigate back from the "activityworld` back to compose land.
Thanks.

Getting Unhandled Exception: [cloud_firestore/permission-denied]. I have checked everything was fine in firebase

Hello Guys I had created the flutter application in which when a user pressed signup button then they will be checked that provided email exists in the firestore database or not if no then they will be move to next screen. I am getting this [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: [cloud_firestore/permission-denied] The caller does not have permission to execute the specified operation. exception even everything looks fine in my code and firestore configuration
Here is my firestore database rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
Here is my project level build.gradle:
buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.13'
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Here is my app-level build.gradle
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.recipedia.fyp.recipedia"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation platform('com.google.firebase:firebase-bom:31.0.3')
}
Here is my signup code:
onPressed: () async {
if (nameTextController.text.isEmpty) {
displayToastMessage(
"Please enter name", context);
} else if (nameTextController.text.length < 3) {
displayToastMessage("Name must be atleast 3 characters", context);
} else if (nameTextController.text.contains(RegExp(r'[0-9]'))) {
displayToastMessage("Numbers and special characters cannot be included", context);
} else if (emailTextController.text.isEmpty) {
displayToastMessage("Please enter email", context);
} else if (!emailTextController.text.contains('#')) {
displayToastMessage("Please enter a valid email", context);
} else if (passwordTextController.text.isEmpty) {
displayToastMessage("Please enter password", context);
} else if (passwordTextController.text.length < 6) {
displayToastMessage("Password must be at-least 6 Characters", context);
} else {
print('Before emailExists');
emailExists = await UserModel().checkIfEmailExists(email);
print('Email exist: $emailExists');
if (emailExists == true) {
snackBar(context, 'Email is already registered');
} else {
/*Move to next screen*/
}
}
}
User Model class checkIfEmailExist function
Future<bool> checkIfEmailExists(String email) async {
try {
var collectionReference = FirebaseFirestore.instance.collection('users');
var doc = await collectionReference.doc(email).get();
return doc.exists;
} catch (e) {
rethrow;
}
}
Solution: In Firestore database rule:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}

Tabbar is not hided on subpages in Ionic 5

In ionic 4 or 5, tabbar is not hided on subpages.
Of course, it works well in ionic 2 or 3.
Please let me know how to solve this issue.
This is my solution.
But hope the best solution.
create TabsService
import this in app.module.ts
Here is full code of TabsService
import { Injectable } from '#angular/core';
import { filter } from 'rxjs/operators';
import { NavigationEnd, Router } from '#angular/router';
import { Platform } from '#ionic/angular';
#Injectable({
providedIn: 'root'
})
export class TabsService {
constructor(private router: Router, private platform: Platform) {
this.platform.ready().then(() => {
this.navEvents();
});
}
public hideTabs() {
const tabBar = document.getElementById('kidesiaTabBar');
if (tabBar && tabBar.style.display !== 'none') {
tabBar.style.display = 'none';
}
}
public showTabs() {
const tabBar = document.getElementById('kidesiaTabBar');
if (tabBar && tabBar.style.display !== 'flex') {
tabBar.style.display = 'flex';
}
}
private navEvents() {
this.router.events
.pipe(filter(e => e instanceof NavigationEnd))
.subscribe((e: any) => {
this.showHideTabs(e);
});
}
private showHideTabs(e: any) {
const urlArray = e.url.split('/');
if (urlArray.length >= 3) {
let shouldHide = true;
if (urlArray.length === 3 && urlArray[1] === 'tabs') {
shouldHide = false;
}
try {
setTimeout(() => (shouldHide ? this.hideTabs() : this.showTabs()), 300);
} catch (err) {}
}
}
}

IONIC-3 NavController throwing can't resolve all parameters error

I have an interesting problem with IONIC-3 that I've not been able to solve. I am attempting to implement an auth routing which is triggered by ionViewCanEnter. However, while I can pass one nav setter, it will not allow multiple. Here is the code:
AuthService Function:
isAuthenticated(nav: NavController): boolean | Promise<any> {
const userAuth = this.uData.getAuthenticated;
const userProfile = this.uData.getUserProfile;
if (userAuth ) {
//User is logged in, so let's check a few things.
if (!userProfile.sign_up_complete) {
//User has not completed sign up
setTimeout(() => { nav.setRoot(CreateAccountPage) }, 0);
}
return true
} else {
//User is not authenticated, return to walkthrough
setTimeout(() => { nav.setRoot(WalkthroughPage) }, 0);
return false
}}
Example calling:
ionViewCanEnter(): boolean | Promise<any> {
return this.auth.isAuthenticated(this.nav);
}
If I have only CreateAccountPage, the script runs fine. However, when I add WalkthroughPage, it throws the following error:
Error: Can't resolve all parameters for ListingPage: (?, [object Object], [object Object], [object Object]).
Which is an error related to the AuthService. For clarity the WalkthroughPage code is as follows:
import { Component, ViewChild } from '#angular/core';
import { IonicPage, NavController, Slides } from 'ionic-angular';
import { RemoteConfigProvider } from '../../providers/remote-config/remote-config';
import { LoginPage } from '../login/login';
import { SignupPage } from '../signup/signup';
#IonicPage()
#Component({
selector: 'walkthrough-page',
templateUrl: 'walkthrough.html'
})
export class WalkthroughPage {
lastSlide = false;
sign_up_enabled: null;
sign_in_enabled: null;
#ViewChild('slider') slider: Slides;
constructor(public nav: NavController,
public remoteConfig: RemoteConfigProvider) {
}
ionViewDidLoad() {
this.remoteConfig.getValue('sign_up_enabled').then(t => {
this.sign_up_enabled = t;
})
this.remoteConfig.getValue('sign_in_enabled').then(t => {
this.sign_in_enabled = t;
})
}
skipIntro() {
this.lastSlide = true;
this.slider.slideTo(this.slider.length());
}
onSlideChanged() {
this.lastSlide = this.slider.isEnd();
}
goToLogin() {
this.nav.push(LoginPage);
}
goToSignup() {
this.nav.push(SignupPage);
}
}
I have attempted to compare both pages, but not identified the exact cause. I welcome any thoughts.
For those who encounter a similar issue, the fix was straight forward. I simply used deep-linking reference which resolved all issues. Example below.
isAuthenticated(nav: NavController): boolean | Promise<any> {
const userAuth = this.userStore.getAuthenticated;
const userProfile = this.userStore.getUserProfile;
if (userAuth) {
return true
} else {
console.log('Auth guard: Not authenticated');
setTimeout(() => { nav.setRoot('no-access') }, 0);
return false
}
}

syntax for verifying if the element is present for protractor serenity

What is the syntax for checking if the element is present? I need to check if the element is present in order to go further in my test. If the element is not present i want to perform some other action.
This is what I am trying but the syntax does not work in serenity:
import {Click, PerformsTasks, Task, step, Wait, Is} from "serenity-js/lib/screenplay-protractor";
import {ProductDetailPageMap} from "../interactions/element-mappings/ProductDetailPageMap";
import { Interaction, UsesAbilities, AnswersQuestions } from "serenity-js/lib/screenplay-protractor";
import { Target, Attribute, BrowseTheWeb } from "serenity-js/lib/screenplay-protractor";
import { ElementArrayFinder, ElementFinder, $$, browser } from "protractor";
export class AddItemToWishlist implements Task {
static called(): AddItemToWishlist {
return new AddItemToWishlist();
}
private parseSizeList(elements: ElementArrayFinder): any {
if (elements.isPresent()) {
Click.on(ProductDetailPageMap.addToWishlist)
} else {
Click.on(ProductDetailPageMap.removeFromWishlist)
browser.sleep(2000);
Click.on(ProductDetailPageMap.addToWishlist)
}
}
#step('{0} Add Item to the Wishlist')
performAs(actor: PerformsTasks): PromiseLike<void> {
return actor.attemptsTo(
//return
);
}
}
I am not sure what to add in the return though??
var logoutButton = element("//button[#type='submit']/span[.='Add to wishlist']");
logoutButton.isPresent().then(function (result) {
if (result) {
// Element IS PRESENT...DO SOMETHING
} else {
// Element IS NOT PRESENT...DO SOMETHING
};
});