How can I use different themes for mobile and main versions Yii? - yii

I have Yii project with main and mobile versions. Views files of mobile version has path
modules/mobile/views/nameController/ . For main version created theme, all views loading from path themes/nameTheme. In config writed 'theme' => 'nameTheme', and in controller I use code:
public function init() {
...
Yii::app()->theme = 'mobile';
...
return parent::init();
}
I moved files of mobile versions to new theme. But Yii loaded views from modules/mobile/views/nameController/. I don't know how define theme for mobile versions in config. Can I use other theme for mobile version in my project (together with theme for main version)?
Thank you in advance.

The way I do my dynamic theme switching, I have method within my Controller component that determines what kind of browser the client uses and then set the theme from within the 'init' method. Very similar to what you've done.
I think the difference is in file organization. If you have separate view files for your desktop and mobile themes, I'd suggest that you place the view files withing the respective theme directories.
I usually make use of a single markup for my themes and just modify the style sheets for both the desktop and mobile themes so I have to worry about it once.
Here's how I do it:
public function isMobileBrowser()
{
$useragent=$_SERVER['HTTP_USER_AGENT'];
return preg_match('/android.+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i',$useragent)||preg_match('/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i',substr($useragent,0,4));
}
And this is my Controller::init method :
public function init()
{
if ($this->isMobileBrowser()) {
Yii::app()->theme = "mobile-white-blue";
}
parent::init();
}
If you have multiple view files scattered around your application, Yii looks first for the view files within your theme folder: AppRoot>>Themes>>{theme_name}>>views and if it can't find it there, Yii looks in the primary view folder: AppRoot>>protected>>views or if it's a module view, AppRoot>>protected>>modules>>{module_name}>>views
I hope that's helpful.

Related

ASP.NET Core 6 MVC - access a view (.cshtml) page from a different class library project

We have a scenario where we have to move couple of our view pages away from our Web API project to a separate class library. Where this class library will be be consumed by different Web API projects that needs to load these shared View pages as part of the functionality. I have been looking for a day now but cannot find a way how to do it.
The view pages work with no problems when accessed from within the Web API project but we have now moved these View pages into our existing common library (a class library) and added it as a reference to the Web API project. Basically when we build the application with the common class library containing the views (we changed the property to Content so it gets added on build time), it gets built and copied into the bin folder. So from this, we could say that the view files should be reachable as its just within the project assembly bin folder.
What happens now is that even if setting the web application builder to specify the Content directory to point to this, it still cannot see the View pages and I get an error
The view was not found
Code:
var builder = WebApplication.CreateBuilder(new WebApplicationOptions {
Args = args,
ContentRootPath = PlatformServices.Default.Application.ApplicationBasePath
});
What bugs me is that the same /Views folder is generated when we put back the View pages back to the Web API project. Same structure and files. Only when put to another project, it now cannot recognize it. Having the view pages on the Web API works while putting it to another project does not.
This is a required structure that we need to implement without the use of a RCL but would still work when referenced by different Web APIs. This may seem odd but this is what we need to do and if possible with only minimal changes.
Your help is very much appreciated!
After a few tries, we we're able to do this by setting the resource object to Embedded Resource and implementing the ManifestEmbeddedFileProvider in the common library to virtually map the location when it gets published as a NuGet. In this case, say like we have a folder named /StaticResources in our common lib. In the sample code below, Program refers to your program assembly or any class object within your application.
Code:
// Get embedded file assembly path to allow our static files to be read by the consuming apps
var manifestEmbeddedProvider = new ManifestEmbeddedFileProvider(
typeof(Program).Assembly,
"/StaticResources");
// Sets the `/StaticResources` folder to be servable like a wwwroot folder
app.UseStaticFiles(new StaticFileOptions {
FileProvider = manifestEmbeddedProvider,
RequestPath = "/Resources"
});
// Use it like this
<script src="/Resources/MyScrtipt.js"></script>
For the View() to work, assuming your views are in /StaticResources folder.
var viewsFileProvider = new ManifestEmbeddedFileProvider(
typeof(Program).Assembly,
"/StaticResources");
app.UseStaticFiles(new StaticFileOptions {
FileProvider = viewsFileProvider,
RequestPath = "/Views/Shared"
});
Hope this helps anyone who comes across this issue. Thank you for all of you who have shared their answers.

How do I handle flavor/target resources in React Native?

I'm building a React Native app which has multiple Targets/Flavors for iOS/Android respectively. So, I have the same code base for both App1 and App2, but they have different logos, launch screens etc. I can't figure out how to add different images to the different versions.
My Android setup in android/app/build.gradle is:
flavorDimensions "appVersion"
productFlavors {
app1 {
applicationId="com.app1name"
dimension "appVersion"
}
app2 {
applicationId="com.app2name"
dimension "appVersion"
}
Then in android/app/src I have a main folder, and an app2 folder, each of which contains a res folder which has the following structure:
res
- drawable-hdpi etc
- mipmap-hpi etc (logos are in here)
- values
When I build app1, it uses the logos etc. from the main folder; if I build app2, it uses the ones from app2 (if they exist).
Likewise for the Targets in XCode, I have 2 Targets, app1 and app2, and have set up the image assets so it pulls in the correct logo etc.
This works fine for the logos and launch screens, but how do I handle images which are to be shown in the app itself? So say the dashboard needs to show Image1.png for App1, and Image2.png for App2 - where should Image1.png and Image2.png be stored?
The main issue is that the image path in React Native can't be a variable. To show an image in React Native I have to use something like:
<Image
source={require('path/to/image')}
/>
I thought I could use a switch statement, so store image_app1.png and image_app2.png in a folder somewhere, and then do something like the following:
switch(appVersion) {
case app1:
imageName = image_app1.png
break
case app2:
imageName = image_app2.png
break
}
and then use imageName to create the path and then require it, but this doesn't work because the path can't be a variable.
The other solution I've seen is to require all images at the outset, and then just show the correct one:
const image1 = require('/path/to/Image1.png')
const image2 = require('/path/to/Image2.png')
switch(appVersion) {
case app1:
<Image source={image1} />
break
case app2:
<Image source={image2} />
break
}
but potentially that involves pre-loading quite a few images (I may later have more app versions, and some of these images might be quite big) and I imagine it could slow things down.
Is there a way to put the images into the appropriate folders in android / ios (so just call the image image.jpg or whatever for all versions, but have different versions in the different folders) and then just refer to image.jpg and let it find the correct one? Or is there a standard way to handle this scenario?
You could use react-native-fs or react-native-blob-util (preferred one, since react-native-fs is no longer maintained), create assets folder inside your target folder and load images from there

Using Favourites with Products in shoutem builder

I've been using / testing the new Shoutem builder, and I've installed both the products and the favourites extensions but am wondering on how I can "link" the two in between. So a user can favourite a specific product and store it in a little dropdown menu. I've searched the documentation and sample apps and I haven't seen the both used in action. Actually I haven't seen the Favourites extension used. Can this be easily accomplished by linking the two extensions?
I am looking for a starting point. So if anyone can guide or link me in the right direction that would be interesting.
Thanks.
This is not documented yet, but we have it implemented. You can check Books extension. It does just what you're looking for. It requires some changes on Product extension. You can check here how you can modify existing extension.
The app folder of extension is what is bundled inside of the app. That said, everything that extension exposes in its app/index.js is the public API, which can be imported directly inside of the other extension:
import {
Screen
} from 'tom.restaurants'
...where tom is used as example for developer name and restaurants for example for extension name.
All extension share the global app state, which is divided into extension sub-states prefixed by extension full name:
{
'tom.restaurnats': {
// state of 'tom.restaurants' extension
}
}
This way, you can make the 2 extension communicate.
I would recommend you checking out these 2 guides:
Technical overview - explains how the extensions are structured inside the app
Modifying extension - explains how to use parts from other extensions inside of your extension

Mobile Specific Views / Device Detection

In the .NET Core docs there is a page titled "Building Mobile Specific Views" but is under construction: https://docs.asp.net/en/latest/mvc/views/mobile.html.
Does anyone have some insight on building mobile views or successfully doing device detection?
Serving specific views based on the browser's user-agent is an outdated concept as it do not sufficiently says much about the capabilities of the device. For example, iPhone and iPad come in different screen sizes and even mobile browsers allow to change the user-agent.
The new concept is called Responsive Design where one creates a single page that fits and show/hides certain element based on the available screen width. One popular responsive deisgn CSS Framework is Bootstrap, originally developed by Twitter and later open-sourced.
Here is an example of responsive design. When you go to the site and change the width of your browser, the design updates as well from 3 to 2 to 1 column design with browser or mobile like navigation (with the Hamburger menu).
This feature actually was not implemented by microsoft. There is couple open discussions for this question:
https://github.com/aspnet/Mvc/issues/4877
https://github.com/aspnet/Razor/issues/751
As a generic answer from them - use responsive web design and css media queries (which from my point of view is not perfect answer for team that claims himself for building general web framework).
There is a implementation for this feature exist as pull request - https://github.com/aspnet/Mvc/pull/4878.
Since this pull request seems to be forgotten, i extract this code into separate project which is available on
https://github.com/laskoviymishka/MvcDeviceDetector.
You may use this implementation (which is easy to add to exist MVC project) or implement this itself. This is pretty easy - you need just implement and reqister own IViewLocationExpander for that.
This can be handle in .Net Core using RazorViewEngineOptions
1) Create Service LocationExpanderService.cs
public class LocationExpanderService : IViewLocationExpander
{
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context,
IEnumerable<string> viewLocations)
{
//replace the Views to MyViews..
var viewLocationsFinal = new List<string>();
if (!string.IsNullOrEmpty(context.Values["viewCustom"]))
{
foreach (var viewLocation in viewLocations)
{
viewLocationsFinal.Add(viewLocation.Replace(".cshtml", ".mobile.cshtml"));
}
}
viewLocationsFinal.AddRange(viewLocations);
return viewLocationsFinal;
}
public void PopulateValues(ViewLocationExpanderContext context)
{
var userAgent = context.ActionContext.HttpContext.Request.Headers["User-Agent"].ToString().ToLower();
var viewCustom = userAgent.Contains("android") || userAgent.Contains("iphone") ? "mobile" : "";
context.Values["viewCustom"] = viewCustom;
}
}
2) Configure services in startup.cs
services.Configure<RazorViewEngineOptions>(o =>
{
o.ViewLocationExpanders.Add(new LocationExpanderService());
});
3) Create view with .mobile extension
Index.mobile.cshtml

Yii - Using alternate view file in Yii User module

Is it possible to use a custom view file in a module (eg. user) in order to keep the module (3rd party) intact?
Somehow extend the module, with a views folder that holds my custom views.
The path to the module theme views should be
/{{your_app_name}}/themes/{{theme_name}}/views/user/
Copy all of the module views from the folder
/{{your_app_name}}/protected/modules/user/views
to the mentioned above folder and that will do the job. After that you could customize the views as you like.
Copy user module view files to <app>/themes/<current_theme>/views/user/. More general, customize module views using the folowing "formula": <app>/themes/<current_tehem>/views/<modules_name>/<controller_name>/<view_file_to_customize>.php
Use a theme. For a module named "user" and a view path of "profile/edit", create "/themes/flashy/user/views/profile/edit.php". You can also define a new layout in "/themes/flashy/layouts/column2.php". Then add to your configuration file in "protected/config":
return array(
// many settings...
'theme' => 'flashy',
For the module "user" you pointed out, unfortunately its controllers use absolute paths for their layouts (e.g. "//layouts/columns2") so AFAIK you can't define distinct layouts for the application and this module.
See also the official guide chapter on theming with Yii.
I disagree that in many help forums of the Internet, when someone asks abot theming a module, everyone suggests a path alias to the themes folder. I think this is wrong, because it implies modules to be splitted, and modules are supposed to be a black-box that can be used across projects. The advice given in such forums would only be valid if a theme is shared among several modules. If someone wants to "package" a theme inside a module, she can:
-add an init function to the controller of the module
-inside that init, use the class attribute layout and a path alias, like this, supose a module whose id is "Sample":
then you add, to SampleCOntroller.php:
public function init() {
//BELOW: it will use the layouts/main.php inside the module.
$this->layouts = "sample.views.layouts.main";
}