Saltar al contenido
Ricardo RetamalSoftware y cosas que vale la pena construir
/
Conversemos
← Ver todos los escritos
AI4 min read

Usando Midjourney con .Net MAUI

Estamos claros que como desarrolladores podemos ser super buenos pero que podamos diseñar aplicaciones atractivas es otra historia…

Publicado originalmente en Medium ↗

English version: https://medium.com/@rretamal.dev/using-midjourney-with-net-maui-a39bbac907a3

Estamos claros que como desarrolladores podemos ser super buenos pero que podamos diseñar aplicaciones atractivas es otra historia totalmente distinta.

En mi caso me gusta hacer aplicaciones y software en general pero al momento de llegar a la interfaz o directamente son feas o comienzo a tomar ideas de distintos sitios hasta que doy con algo que podría ser considerado como medianamente aceptable.

Con todo el boom de las AIs quería hacer una prueba con Midjourney y ver hasta que punto podrían llegar, así que por que no?

Lo primero que tienes que saber es que esta disponible a través de un bot en Discord donde interactuas con él. En caso de que quieras profundizar mas con Midjourney te dejo el siguiente link:

How to use Midjourney: Sign up process, prompts, and more
Midjourney is one of the most popular AI-art generator programs out there. However, you can only access it through…www.windowscentral.com

En este caso quería diseñar una app para visualización de cámaras, si lo se, otra vez, así que rápido como el rayo le pedí lo primero que me vino a la mente, mi petición fué la siguiente:

“/imagine ip camera viewer, mobile app,user interface, highly detailed, hq, one main section to view camera, with small options to add a new one, alerts option, clean ui, figma”

El resultado fue el siguiente:

No se parecía a algo que me tuviera en mi mente así que refine un poco mas la búsqueda:

/imagine https://s.mj.run/kpnL-LUJn2g ip camera viewer, mobile app,user interface, highly detailed, hq, one main section to view camera, with small options to add a new one, alerts option, clean ui, figma

En este caso le indique una url para que se basara en un diseño previo, siguiendo la misma línea quería una app orientada a la visualización de cámaras ip.

El resultado fue el siguiente:

Esto ya me comenzó a gustar mas, así que con el objetivo de presentar un demo me fuí por la primera opción. Midjourney te permite seguir iterando y generando variaciones sobre un diseño en particular pero en este caso no fue necesario.

Ya una vez elegido el diseño el siguiente paso fue crear el proyecto con .Net MAUI, para ello en Visual Studio 2022 creas un nuevo proyecto seleccionando .Net MAUI App.

La estructura de la solución debe ser como la siguiente:

Basado en la imagen tenemos 4 grandes secciones, un panel de búsqueda, la visualización de la cámara, una sección con un mensaje y un tab panel en la parte inferior.

Para el layout principal seleccioné un AbsoluteLayout que me permita ubicar elementos por encima de la vista de la cámara de esta manera se puede crear ese efecto de ver imagenes mientra mostramos mensajes, opciones, etc.

Cada sección estará formada por un Grid que tendrá sub elementos y para aquellos controles circular usé frames.

Para el tab necesitaba un control que me permitiera customizar mas la parte visual así que seleccione Sharpnado Tabs para MAUI:

GitHub - roubachof/Sharpnado.Tabs: Pure MAUI and Xamarin.Forms Tabs, including fixed tabs…
Pure MAUI and Xamarin.Forms Tabs, including fixed tabs, scrollable tabs, bottom tabs, badge, segmented control, custom…github.com

Para los iconos integré FontAwesome en la solución.

Para extraer los códigos hexadecimales de la imagen y trabajar con la misma escala de colores en la app usé la siguiente herramienta:

👍 Color Picker online | HEX Color Picker | HTML Color Picker
Color Picker:Color Picker: With this online tool you can upload an image or provide a website URL and get the RGB…imagecolorpicker.com

Te permite subir una imagen e ir obteniendo los códigos de los colores de cualquier área.

El xaml inicial luce de la siguiente manera con las secciones principales definidas:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:tabs="http://sharpnado.com"
             x:Class="MidjourneyCameraApp.MainPage" NavigationPage.HasNavigationBar="False">
    <ContentPage.Resources>
        <ResourceDictionary>
            <Color x:Key="Gray900">#212121</Color>
            <Color x:Key="White">#fefefe</Color>
            <Color x:Key="MenuGray">#4b4b51</Color>
            <Color x:Key="Orange">#ff7052</Color>
        </ResourceDictionary>
    </ContentPage.Resources>
    <AbsoluteLayout>
        <Image Source="living2.png" Aspect="Fill" Margin="0"/>
        <Grid HorizontalOptions="FillAndExpand" AbsoluteLayout.LayoutBounds="0.1,0,1,.9" AbsoluteLayout.LayoutFlags="All">
            // Search Box
        </Grid>
        <Label Text="Living room" TextColor="White" FontSize="20" FontAttributes="Bold" AbsoluteLayout.LayoutBounds="0.1,5.7,0.8,.9" AbsoluteLayout.LayoutFlags="All"></Label>
        <Frame CornerRadius="0" Padding="0" BackgroundColor="#60000000" BorderColor="Transparent" HorizontalOptions="FillAndExpand" AbsoluteLayout.LayoutBounds="0.5,0.9,400,250" AbsoluteLayout.LayoutFlags="PositionProportional">
            // Messages section
        </Frame>
        <Grid AbsoluteLayout.LayoutBounds="0.5,1,390,160"
                AbsoluteLayout.LayoutFlags="PositionProportional">
            // Tab panel section
        </Grid>
    </AbsoluteLayout>
</ContentPage>

GitHub Gist ↗

Seguidamente trabajaremos en el tab panel, dicha sección esta compuesta del menú y de un panel en la parte superior.

<Grid AbsoluteLayout.LayoutBounds="0.5,1,390,160"
                AbsoluteLayout.LayoutFlags="PositionProportional">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Frame Grid.Row="0" HasShadow="True" BackgroundColor="{StaticResource White}" HeightRequest="60" Margin="15,0,15,-10" CornerRadius="15" BorderColor="Transparent">
                <Frame.Shadow>
                    <Shadow Brush="Black"
                Offset="60,60"
                Radius="80"
                Opacity="0.8" />
                </Frame.Shadow>
                <Label Text="Current Status: Streaming cam" TextColor="{StaticResource MenuGray}"/>
            </Frame>
            <Frame HasShadow="True" Grid.Row="1" Padding="0" BackgroundColor="{StaticResource White}" BorderColor="#fbefea" HeightRequest="120">
                <Frame.Shadow>
                    <Shadow Brush="Black"
                Offset="60,60"
                Radius="80"
                Opacity="0.8" />
                </Frame.Shadow>
                <tabs:TabHostView Padding="0"  x:Name="TabHost" VerticalOptions="CenterAndExpand">
                    <tabs:MaterialUnderlinedTabItem Style="{StaticResource MaterialTabStyle}"
                                                            IconImageSource="house_96.png"
                                                            IconOptions="TopIcon"
                                                            IconTextSpacing="5"
                                                            IconSize="35"
                                                            UnselectedLabelColor="{StaticResource MenuGray}"                                                            
                                                            Label="Live View" />
                    <tabs:MaterialUnderlinedTabItem Style="{StaticResource MaterialTabStyle}"
                                                            IconImageSource="house_96.png"
                                                            IconSize="35"
                                                            IconOptions="TopIcon"
                                                            IconTextSpacing="5"
                                                            UnselectedLabelColor="{StaticResource MenuGray}"
                                                            Label="Alerts" />
                    <tabs:MaterialUnderlinedTabItem Style="{StaticResource MaterialTabStyle}"
                                                            IconImageSource="house_96.png"
                                                            IconOptions="TopIcon"
                                                            IconTextSpacing="5"  
                                                            IconSize="35"
                                                            UnselectedLabelColor="{StaticResource MenuGray}"
                                                            Label="Configuration" />
                </tabs:TabHostView>
            </Frame>
        </Grid>

GitHub Gist ↗

Ya una vez con el panel funcionando podemos enfocarnos en el resto de las secciones, el xaml resultante sería el siguiente:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:tabs="http://sharpnado.com"
             x:Class="MidjourneyCameraApp.MainPage" NavigationPage.HasNavigationBar="False">
    <ContentPage.Resources>
        <ResourceDictionary>
            <Color x:Key="Gray900">#212121</Color>
            <Color x:Key="White">#fefefe</Color>
            <Color x:Key="MenuGray">#4b4b51</Color>
            <Color x:Key="Orange">#ff7052</Color>
            <Style x:Key="MaterialTabStyle" TargetType="tabs:MaterialUnderlinedTabItem">
                <Setter Property="SelectedTabColor" Value="{StaticResource Primary}" />
                <Setter Property="IconSize" Value="24" />
                <Setter Property="FontFamily" Value="OpenSansRegular" />
                <Setter Property="LabelSize" Value="14" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>
    <AbsoluteLayout>
        <Image Source="living2.png" Aspect="Fill" Margin="0"/>
        <Grid HorizontalOptions="FillAndExpand" AbsoluteLayout.LayoutBounds="0.1,0,1,.9" AbsoluteLayout.LayoutFlags="All">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Label Text="My Camera Viewer" TextColor="White" Margin="20,20,0,0" FontSize="20" FontAttributes="Bold"></Label>
            <Frame x:Name="frame" Grid.Row="1" Padding="0" Margin="10,20,10,0" CornerRadius="20" HeightRequest="60" BackgroundColor="#80000000">                
                <StackLayout Orientation="Horizontal" VerticalOptions="FillAndExpand">
                    <Label Text="Type your Search" TextColor="White" HorizontalOptions="StartAndExpand" VerticalOptions="CenterAndExpand" Margin="20,0,0,0" FontSize="15"></Label>
                    <Frame HorizontalOptions="EndAndExpand" BackgroundColor="{StaticResource Orange}" BorderColor="Transparent" CornerRadius="40" HeightRequest="40" WidthRequest="40" Margin="0,0,20,0" Padding="0">
                        <Image HorizontalOptions="Center" VerticalOptions="Center">
                            <Image.Source>
                                <FontImageSource
                                FontFamily="FASolid"
                                Glyph="{StaticResource search}"
                                Color="White"
                                Size="15"/>
                            </Image.Source>
                        </Image>
                    </Frame>
                </StackLayout>
            </Frame>
        </Grid>
        <Label Text="Living room" TextColor="White" FontSize="20" FontAttributes="Bold" AbsoluteLayout.LayoutBounds="0.1,5.7,0.8,.9" AbsoluteLayout.LayoutFlags="All"></Label>
        <Frame CornerRadius="0" Padding="0" BackgroundColor="#60000000" BorderColor="Transparent" HorizontalOptions="FillAndExpand" AbsoluteLayout.LayoutBounds="0.5,0.9,400,250" AbsoluteLayout.LayoutFlags="PositionProportional">
            <Frame x:Name="messageFrame" Padding="0" CornerRadius="5" BackgroundColor="#2c2932" BorderColor="Transparent" HeightRequest="110" VerticalOptions="StartAndExpand" HorizontalOptions="FillAndExpand" Margin="0,20,0,0">
                <StackLayout Orientation="Horizontal" VerticalOptions="CenterAndExpand" >
                    <Frame HorizontalOptions="Start" VerticalOptions="CenterAndExpand" BackgroundColor="{StaticResource Orange}" BorderColor="Transparent" CornerRadius="40" HeightRequest="80" WidthRequest="80" Margin="20,0,0,0">
                        <Image>
                            <Image.Source>
                                <FontImageSource
                                FontFamily="FASolid"
                                Glyph="{StaticResource exclamation}"
                                Color="White"
                                Size="24"/>
                            </Image.Source>
                        </Image>
                    </Frame>
                    <StackLayout Orientation="Vertical" HorizontalOptions="StartAndExpand" Margin="10,0,0,0" VerticalOptions="Center">
                        <Label Text="Warning Title" TextColor="{StaticResource Orange}" FontSize="20" />
                        <Label Text="Detail of the message indicating the error" TextColor="#72727a" FontSize="13" />
                    </StackLayout>
                </StackLayout>
            </Frame>
        </Frame>
        <Grid AbsoluteLayout.LayoutBounds="0.5,1,390,160"
                AbsoluteLayout.LayoutFlags="PositionProportional">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Frame Grid.Row="0" HasShadow="True" BackgroundColor="{StaticResource White}" HeightRequest="60" Margin="15,0,15,-10" CornerRadius="15" BorderColor="Transparent">
                <Frame.Shadow>
                    <Shadow Brush="Black"
                Offset="60,60"
                Radius="80"
                Opacity="0.8" />
                </Frame.Shadow>
                <Label Text="Current Status: Streaming cam" TextColor="{StaticResource MenuGray}"/>
            </Frame>
            <Frame HasShadow="True" Grid.Row="1" Padding="0" BackgroundColor="{StaticResource White}" BorderColor="#fbefea" HeightRequest="120">
                <Frame.Shadow>
                    <Shadow Brush="Black"
                Offset="60,60"
                Radius="80"
                Opacity="0.8" />
                </Frame.Shadow>
                <tabs:TabHostView Padding="0"  x:Name="TabHost" VerticalOptions="CenterAndExpand">
                    <tabs:MaterialUnderlinedTabItem Style="{StaticResource MaterialTabStyle}"
                                                            IconImageSource="house_96.png"
                                                            IconOptions="TopIcon"
                                                            IconTextSpacing="5"
                                                            IconSize="35"
                                                            UnselectedLabelColor="{StaticResource MenuGray}"                                                            
                                                            Label="Live View" />
                    <tabs:MaterialUnderlinedTabItem Style="{StaticResource MaterialTabStyle}"
                                                            IconImageSource="house_96.png"
                                                            IconSize="35"
                                                            IconOptions="TopIcon"
                                                            IconTextSpacing="5"
                                                            UnselectedLabelColor="{StaticResource MenuGray}"
                                                            Label="Alerts" />
                    <tabs:MaterialUnderlinedTabItem Style="{StaticResource MaterialTabStyle}"
                                                            IconImageSource="house_96.png"
                                                            IconOptions="TopIcon"
                                                            IconTextSpacing="5"  
                                                            IconSize="35"
                                                            UnselectedLabelColor="{StaticResource MenuGray}"
                                                            Label="Configuration" />
                </tabs:TabHostView>
            </Frame>
        </Grid>
    </AbsoluteLayout>
</ContentPage>

GitHub Gist ↗

El resultado comienza a lucir de la siguiente manera:

El código lo pueden conseguir por aquí:

rretamal/MidjourneyCameraApp (github.com)

A propósito la imagen inicial con las cámaras tambien fue generada por Midjourney!

Si quieres ver el diseño en uso puedes descargar el ejemplo desde aquí:

Android:

ReCam Viewer — Apps on Google Play

Happy Coding!