Xamarin.Forms - Switching between Dark & Light themes at run time - xamarin.forms

1 - Any idea if we can switch between Light & Dark themes using the Xamarin.Forms themes introduced in version 2.3.x (link below). Any workaround?
https://developer.xamarin.com/guides/xamarin-forms/user-interface/themes/
2 - Also I see this release is in preview ever since it was introduced. Are there any issues and we cannot use it in production?

The accepted answer doesn't adhere to the convention demonstrated by Microsoft.
Assuming you've installed the packages, Xamarin.Forms.Themes.Base, Xamarin.Forms.Themes.Light, and Xamarin.Forms.Themes.Dark, and your App.xaml looks like,
<?xml version="1.0" encoding="utf-8" ?>
<Application
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:light="clr-namespace:Xamarin.Forms.Themes;assembly=Xamarin.Forms.Theme.Light"
xmlns:dark="clr-namespace:Xamarin.Forms.Themes;assembly=Xamarin.Forms.Theme.Dark"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyNamespace.MyApp">
<Application.Resources>
<ResourceDictionary MergedWith="light:LightThemeResources">
...
</ResourceDictionary>
</Application.Resources>
</Application>
you can change the theme at run time with the following:
public enum Themes
{
Dark,
Light
}
var origin = App.Current.Resources;
switch (theme)
{
case Themes.Dark:
origin.MergedWith = typeof(DarkThemeResources);
break;
case Themes.Light:
origin.MergedWith = typeof(LightThemeResources);
break;
}

Yes is possible, adding the Resources by code in the App.cs class you can switch which Theme to use.
In the class constructor you set the default Theme:
Resources = new Xamarin.Forms.Themes.DarkThemeResources ();
You then expose a method is this class SwitchTheme() where you will assign the other theme:
public void SwitchTheme ()
{
if (Resources?.GetType () == typeof (DarkThemeResources))
{
Resources = new LightThemeResources ();
return;
}
Resources = new DarkThemeResources ();
}
Be aware that if you have defined styles the code above won't work as it will override your Resources Dictionary. For that you could create your own themes based on these two, adding your define styles and use your implementations for switching.

Related

How to configure ABP Framework to use Bootswatch in RTL languages

With a reference to Jonathan Potts ABP Bootswatch article there is a clear and good approach to switching Bootstrap with Bootswatch in ABP Framework. However, it is not working when it comes to using Bootswatch in RTL languages like Arabic or Farsi.
I wonder what am I missing and what is the best practice to solve this problem?
Project's UI is based on MVC Razor Pages
Here is my BundleContributor:
public class BootswatchStyleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
var theme = "minty";
var bootstrap = "/libs/bootstrap/css/bootstrap.css";
var bootswatch = $"/libs/bootswatch/{theme}/bootstrap.css";
context.Files.ReplaceOne(bootstrap, bootswatch);
}
}
And Configuration:
private void ConfigureBundles()
{
Configure<AbpBundlingOptions>(options =>
{
options.StyleBundles.Configure(
BasicThemeBundles.Styles.Global,
bundle =>
{
bundle.AddFiles("/global-styles.css");
bundle.AddContributors(typeof(BootswatchStyleContributor));
}
);
});
}
Here is the result in LTR which is working fine:
But not work after changing the language to an RTL language such as Arabic:
i used #Engincan idea
using CultureHelper.IsRtl is fine

Xamarin swap single ResourceDictionary programmatically

I'm currently trying to get custom themes working with a Xamarin App and I've found a way I can make this work the way I want it to, but it seems terribly inefficient, so figured I'd ask to see if you lot had better answers.
So here's the issue, I want to build a screen where users can change themes (there will be many, not just light/dark), and I want to swap out the current theme ResourceDictionary and replace it with the new chosen theme's ResourceDictionary. The problem is I also want the App to have other global ResourceDictionaries, and every tutorial I find recommends calling Resources.MergedDictionaries.Clear(), but that then gets rid of my other ResourceDictionaries.
Here is my Xaml, I don't want to replace DefaultButton.xaml or LinkButton.xaml, I only want to replace LightTheme.xaml.
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="myTestApp.App">
<Application.Resources>
<ResourceDictionary Source="Themes/ResourceDictionaries/LightTheme.xaml" />
<ResourceDictionary Source="Styles/DefaultButton.xaml" />
<ResourceDictionary Source="Styles/LinkButton.xaml" />
</Application.Resources>
</Application>
Then in my App class I have
public void ChangeTheme(ThemeOptions theme)
{
ResourceDictionary newRes;
switch (theme)
{
case ThemeOptions.Light:
newRes = new LightTheme();
break;
case ThemeOptions.Dark:
newRes = new DarkTheme();
break;
default:
newRes = new LightTheme();
break;
}
Resources.MergedDictionaries.Clear();
Resources.Add(newRes);
}
Now obviously the DefaultButton.xaml and LinkButton.xaml ResourceDictionaries have now been cleared. I can add them back, but it seems terribly inefficient over just finding and removing the Theme. Is there an easy way to simply identify the ResourceDictionary I want to overwrite and replace that?
I believe I may have discovered the answer to my problem, but not to the original question. I created a "BundledSiteStyles" ResourceDictionary, and simply added that as a MergedDictionary to each of my themes. Then the App.xaml file only loads a theme and all the styling comes along for the ride.
So my App.xaml has no ResourceDictionaries added, then programmatically on startup I get the active theme and call my ChangeTheme function
public void ChangeTheme(ThemeOptions theme)
{
switch (theme)
{
case ThemeOptions.Light:
Resources = new LightTheme();
break;
case ThemeOptions.Dark:
Resources = new DarkTheme();
break;
default:
Resources = new LightTheme();
break;
}
}
Here is my LightTheme.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="myTestApp.Themes.ResourceDictionaries.LightTheme">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../../Styles/BundledSiteStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
<Color x:Key="LinkButtonBackgroundColor">Transparent</Color>
<Color x:Key="LinkButtonTextColor">Blue</Color>
<!-- Add more colours here -->
</ResourceDictionary>
And here is my BundledSiteStyles.xaml
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="myTestApp.Styles.BundledSiteStyles">
<ResourceDictionary.MergedDictionaries>
<!-- Add global ResourceDictionaries Here -->
<ResourceDictionary Source="DefaultButton.xaml" />
<ResourceDictionary Source="LinkButton.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
If there is a more efficient/correct way of doing this, please let me know, but for now it seems to work.
Assuming all the themes have the same keys, you can replace it by iterating through its keys, and replacing those entries in the merged dictionary.
I haven't tested, but should be something like this:
var resources = Application.Current.Resources;
foreach (var key in themes.Keys)
resources[key] = themes[key];

Storybook + Vue3 - Error when trying to use custom directives

When trying to use custom directives with Vue3 and storybook, I get this error:
I don't understand the issue and have no idea where to even start to look. I'm still very new to Vue and storybook.
I created a small test directive just to make sure it wasn't something to do with a more complicated one:
app.directive('red-bg', {
beforeMount: (element, binding) => {
element.style.backgroundColor = "red";
}
});
and applied it:
<div class="wmr-select relative" ref="selectRef" v-red-bg>
It works in the normal app part of the the project (as you can see with the red bg):
But in story book I get the error in the first image.
I haven't been able to find any kind of answer for this.
Hoping someone will swoop in and save me.
Thanks.
Since Storybook is using another file to initialize your app, you need to define the directive in both file.
This is explained in the configuring storybook section of the doc.
In my case, I had defined the directive in my main.js file, but I also had to define it in the preview.js file, of the .storybook folder.
As a reference, here is was my .storybook/preview.js looks like:
import { app } from "#storybook/vue3";
/* all the other import needed for my project **/
import store from "#/store";
/** ... */
export const parameters = {
/** Some parameters specifics to the plugins of storybook. **/
/** For example, when using themes. **/
};
/** App specific initialization, ex defining locale */
const i18n = createI18n({
locale: "en",
fallbackLocale: "en",
messages: myLocaleMessageLoader()
});
/** registering directives */
app.directive("my-custom-directive", myCustomDirectiveHandler);
app
.use(i18n)
.use(store)
/** all the other `.use` that my app need.*/
Please note the usage of storybook own app in the import.
After adding the directive in the .storybook/preview.js I was successfully able to use it in my stories.

Implementing Theming in React Application

I got a requirement from my client that we need to have custom themes in our react application.
Where user can select one of the available themes of his choice which will change some css attribute like font-color, font-family etc.
Right now I apply css rules to my application using className attribute.
I tried searching some of the ways of implementing the same and found one way that is having alternate css
<link rel="stylesheet" href="main.css">
<link rel="stylesheet alternate" href="light.css" id="light" title="Light">
<link rel="stylesheet alternate" href="dark.css" id="dark" title="Dark">
<script>
function enableStylesheet (node) {
node.rel = 'stylesheet';
}
function disableStylesheet (node) {
node.rel = 'alternate stylesheet';
}
</script>
And toggle rel value based on user selection.
Can any one suggest me any other way of achieving the same.
Thanks in advance.
Your usage is the correct way of implementing. But what if you've numerous theme? Loading all the css files will hamper the website. So, I would suggest you to update the source instead of loading all css files and enabling/disabling them.
node.ref = 'dark.css';
This way, you're requiring the css file only when in use. Once, the file is being used, next time it's cached. So no need to worry about them later. Obviously, it might take some time and may impact on performance if the filesize is huge, at initial use. Even though the best thing is that you don't need to wait for all.
But wait!!!
You're using react. React provides us context api and I would obviously utilize this in such scenario. Here's an excerpted example:
import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';
class App extends React.Component {
constructor(props) {
super(props);
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
// State also contains the updater function so it will
// be passed down into the context provider
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme,
};
}
render() {
// The entire state is passed to the provider
return (
<ThemeContext.Provider value={this.state}>
<Content />
</ThemeContext.Provider>
);
}
}
function Content() {
return (
<div>
<ThemeTogglerButton />
</div>
);
}
ReactDOM.render(<App />, document.root);
Similar concept can be implemented using redux and with logger information to make our work easy.

How to customize the Document List in Alfresco?

In the repository there be will different document lists.i.e there will be Data dictionary, user homes,Guest homes etc. when I change the view to "detailed view", it displays Favorite, like, comments links. where will I have to modify if I dont want to show them. Can you tell in which file i have to comment the code for not displaying those links. Thank you in Advance.
I wanted a "modular" answer to this question, this answer is to show how I handled this issue.
Context: Alfresco 4.2.f, project Maven from the org.alfresco.maven.archetype:alfresco-amp-archetype:1.1.1 archetype, I put everything in the embedded JAR when possible.
Create a module extension for share (see this blog for more details). Here is my extension file:
src/main/resources/alfresco/site-data/extensions/my-custom-extension.xml
<extension>
<modules>
<module>
<id>Main module of my custom extension</id>
<version>${project.version}</version>
<auto-deploy>true</auto-deploy>
<customizations>
<customization>
<!-- Order matters here! target before source, always! -->
<targetPackageRoot>org.alfresco</targetPackageRoot>
<sourcePackageRoot>my-custom.main</sourcePackageRoot>
</customization>
</customizations>
</module>
</modules>
</extension>
In the documentlibrary component of your module package, create this FTL in order to declare a javascript:
src/main/resources/alfresco/site-webscripts/my-custom/main/components/documentlibrary/documentlist-v2.get.html.ftl
<#-- Add a Javascript declaration -->
<#markup id="my-custom-js" target="js" action="after">
<#script type="text/javascript" group="documentlibrary"
src="${url.context}/res/my-custom/main/components/documentlibrary/documentlist.js"/>
</#>
In the resources (META-INF), under documentlibrary component, create the Javascript:
src/main/resources/META-INF/my-custom/main/components/documentlibrary/documentlist.js
YAHOO.lang.augmentObject(Alfresco.DocumentList.prototype, {
// Possible values: i18nLabel, lockBanner, syncFailed, syncTransientError
// date, size, name, version, description, tags, categories
myCustomDisabledRenderers: ["description", "version", "tags"],
// Possible values: favourites, likes, comments, quickShare
myCustomDisabledSocials: ["favourites", "comments", "likes", "quickShare"],
myCustomIsSocialDisabled: function(propertyName) {
return Alfresco.util.arrayContains(
this.myCustomDisabledSocials, propertyName);
},
myCustomIsRendererDisabled: function(propertyName) {
if (Alfresco.util.arrayContains(
this.myCustomDisabledRenderers, propertyName)) {
return true;
}
// Disable the social renderer when all the social features are
// disabled
if (propertyName === "social" && this.myCustomDisabledSocials.length == 4) {
return true;
}
return false;
},
/** Helper function to disable socials
* propertyName must be one of "favourites", "comments", "likes", "quickShare"
*/
myCustomDisableSocial: function(propertyName) {
if (!Alfresco.util.arrayContains(
this.myCustomDisabledSocials, propertyName)) {
this.myCustomDisabledSocials.push(propertyName);
}
},
// Custom registerRenderer for social features, originally defined in:
// webapps/share/components/documentlibrary/documentlist.js:2134
myCustomSocialRegisterRenderer: function(record) {
var jsNode = record.jsNode;
var html = "";
// Current usage of the separator variable allow to change the order
// of the different social features (the 'if' blocks below) without
// changing their content
var separator = "";
/* Favourite / Likes / Comments */
if (!this.myCustomIsSocialDisabled("favourites")) {
html += '<span class="item item-social' + separator + '">' +
Alfresco.DocumentList.generateFavourite(this, record) +
'</span>';
separator = " item-separator";
}
if (!this.myCustomIsSocialDisabled("likes")) {
html += '<span class="item item-social' + separator + '">' +
Alfresco.DocumentList.generateLikes(this, record) +
'</span>';
separator = " item-separator";
}
if (!this.myCustomIsSocialDisabled("comments") &&
jsNode.permissions.user.CreateChildren) {
html += '<span class="item item-social' + separator + '">' +
Alfresco.DocumentList.generateComments(this, record) +
'</span>';
separator = " item-separator";
}
if (!this.myCustomIsSocialDisabled("quickShare") && !record.node.isContainer &&
Alfresco.constants.QUICKSHARE_URL) {
html += '<span class="item' + separator + '">' +
Alfresco.DocumentList.generateQuickShare(this, record) +
'</span>';
separator = " item-separator";
}
return html;
},
// Overwrite registerRenderer which was originally defined in:
// webapps/share/components/documentlibrary/documentlist.js:1789
registerRenderer: function DL_registerRenderer(propertyName, renderer) {
if (Alfresco.util.isValueSet(propertyName) &&
Alfresco.util.isValueSet(renderer) &&
!this.myCustomIsRendererDisabled(propertyName)) {
if (propertyName === "social") {
this.renderers[propertyName] = this.myCustomSocialRegisterRenderer;
} else {
this.renderers[propertyName] = renderer;
}
return true;
}
return false;
}
}, true);
Then you can disable the links by updating myCustomDisabledRenderers and/or mySocialDisabledRenderers.
This way also allows you to create a module that disable (for example) the "comments on documents" or "likes on document" feature independently in only 6 easy steps!
Example, how to make a module that only disable comments on documents in 6 steps
Important: first remove the "comment disabling" from the documentlist.js of the main module.
myCustomDisabledSocials: ["favourites", "likes", "quickShare"],
Create a new module "my-custom.nocomment" with the same structure.
<extension>
<modules>
<module>
<id>Main module of my custom extension</id>
[...]
</module>
<module>
<id>No comment module of my custom extension</id>
<version>${project.version}</version>
<customizations>
<customization>
<targetPackageRoot>org.alfresco</targetPackageRoot>
<sourcePackageRoot>my-custom.nocomment</sourcePackageRoot>
</customization>
</customizations>
</module>
</modules>
</extension>
Add the FTL...
src/main/resources/alfresco/site-webscripts/my-custom/nocomment/components/documentlibrary/documentlist-v2.get.html.ftl
<#-- Add a Javascript declaration -->
<#markup id="my-custom-js" target="js" action="after">
<#script type="text/javascript" group="documentlibrary"
src="${url.context}/res/my-custom/nocomment/components/documentlibrary/documentlist.js"/>
</#>
then the Javascript...
src/main/resources/META-INF/my-custom/nocomment/components/documentlibrary/documentlist.js
Alfresco.DocumentList.prototype.myCustomDisableSocial("comment");
and then I'm happy, clap along if you feel like everything's just got smooth!
Notes:
The nocomment module depends on the main module.
It is important for the nocomment module to be loaded after the main module (in http://localhost:8080/share/page/modules/deploy).
In order for the nocomment module to be complete, you also need to disable comments from the document details page, see below.
Disable comments from the document details page
Even if, this one is documented elsewhere, I spent so much time searching around these few days that I feel like I need to be as comprehensive as possible.
src/main/resources/alfresco/site-data/extensions/my-custom-extension.xml
Add this to your my-custom.nocomment module declaration and you will get rid of the comments form and list from the document details page.
[...]
<module>
<id>No comment module of my custom extension</id>
[...]
<components>
<component>
<region-id>comments</region-id>
<source-id>document-details</source-id>
<scope>template</scope>
<sub-components>
<sub-component id="default">
<evaluations>
<evaluation id="guaranteedToHide">
<render>false</render>
</evaluation>
</evaluations>
</sub-component>
</sub-components>
</component>
</components>
</module>
[...]
src/main/resources/alfresco/site-webscripts/my-custom/nocomment/components/node-details/node-header.get.js
This is for disabling the button on the header of the document details page.
// Disable comments
for (var i = 0; i < model.widgets.length; i++) {
if (model.widgets[i].id == "NodeHeader") {
model.widgets[i].options.showComments = false;
}
}
// And since it does not work, disable comments this way too
model.showComments = "false";
Note: I did not test these snippets, they have been taken from my project after "anonymization" (basically renaming the module). Let me know if you find mistakes.
What you are looking for is more than likely generated by client-side JavaScript. You should use share-config-custom.xml to set Share to development mode, like this:
<alfresco-config>
<!-- Put Share Client in debug mode -->
<config replace="true">
<flags>
<client-debug>true</client-debug>
<client-debug-autologging>false</client-debug-autologging>
</flags>
</config>
</alfresco-config>
Then, use firebug or your browser's developer console to step through the client-side JavaScript. You should be able to find the point where the document library elements are rendered.
You can override Alfresco's client-side JavaScript components with your own components. Please put them in your own namespace to avoid collisions with Alfresco's.
I did it by commenting the {social} line in file share-documentlibrary-config.xml in share/src/alfresco/share-document-config
...
<metadata-templates>
<!-- Default (fallback) -->
<template id="default">
<line index="10" id="date">{date}{size}</line>
<line index="20" id="description" view="detailed">{description}</line>
<line index="30" id="tags" view="detailed">{tags}</line>
<line index="40" id="categories" view="detailed" evaluator="evaluator.doclib.metadata.hasCategories">{categories}</line> -->
<!-- <line index="50" id="social" view="detailed">{social}</line> -->
</template>
...
It works!
I'm not sure if I understand well your question - you're trying to hide some columns from particular view in alfresco explorer? If so, you need to edit /jsp/browse/browse.jsp file, but I think that's not a good idea. Maybe implementing your own NodePropertyResolver should be better way, have look at my older blogpost on this topic: http://www.shmoula.cz/adding-columns-to-custom-browse-jsp/
It looks like all of it is in: \opt\alfresco-4.0.d\tomcat\webapps\share\components\documentlibrary\documentlist.js
I think the trick is in this.registerRenderer("social"...) to return html before line 1981 (after favorites before likes) supposing you want to keep at least faorite

Resources