Delphi 10.1 Berlín Starter Edition de 392 a 0 Euros



Como parte de Delphi Boot Camp, Embarcadero ofrece la versión Delphi 10.1 Berlín Starter Edition por 0 Euros.
Delphi Boot Camp son unas jornadas profesionales ( del 5 al 9 de Septiembre ) realizadas por expertos muy conocidos en Delphi, como son:


David Intersimone 
David I.
Evangelist & Educator
Embarcadero Technologies

 Jim McKeeth 
Jim McKeeth
Evangelist & Engineer
Embarcadero Technologies

 Marco Cantu 
Marco Cantu
Product Manager
Embarcadero Technologies

 Eli M.
Eli M.
Developer & Entrepreneur
FMXExpress.com


 La agenda para estos días es la siguiente:


Agenda for the week  

Day 1: Introduction to Delphi: The IDE and Your First App

Day 2: Getting to Know the Delphi Language

Day 3: Building Effective User Interfaces with FireMonkey

Day 4: Game Development with Delphi

Day 5: Stepping up to Mobile and Database Development



El link para registrarse es:

[Register TODAY]


Una vez registrado puedes solicitar gratuitamente una copia de Delphi 10.1 Berlín Starter Edition desde aquí

Y por si esto no fuera poco puedes pedir también gratuitamente una copia del libro Marco Cantu's Object Pascal Handbook 




Una app para visualizar moléculas

"Molecule Hero" 3D Interactive Chemical Molecule Viewer

A continuación os presento una app llamada "Molecule Heroque permite ver moléculas en 3D , su autor es  Pawel Glowacki

Tiene entre otras opciones la posibilidad de mostrar el "modelo de alambre"  p.ej, en el caso de la molécula de la Cafeína sería el siguiente:


La app está disponible en Play Store (abajo tenéis el link) y  el código fuente está en GitHub bajo licencia MIT de código abierto por lo que se puede reutilizar el código indicando su procedencia.
Utiliza la renderización de esferas (para mostrar los átomos de la molécula) en tiempo real, además permite girar desde todos los ángulos el modelo y ver las conexiones entre los diferentes átomos.
Los datos los obtiene desde el "Protein Data Bank" PDB que es el formato estándar de archivo para el intercambio de datos de estructuras de moléculas.
Así que si queréis ver cómo es la molécula de la cafeína, del colesterol o del diamante, entre otras muchas, esta es vuestra app.

Pawel Glowacki forma parte del equipo de Embarcadero como "Technical Lead for Developer Tools", aquí tenéis su Twitter 



Link de la app Molecule Hero en Play Store



Quizás te pueda interesar:

Programación paralela con Delphi
Obtener datos de los satelites que utiliza el gps de tu dispositivo
Rotaciones en 3D
Obtener la elevación en un punto del mapa

Un app para mostrar moléculas en 3D


Código para programadores de apps Android

A continuación tienen una lista de trucos que seguro les serán de utilidad para aquellos que se dediquen a la programación de aplicaciones para móviles Android:


ÍNDICE:

 -OBTENER EL IMEI
 -CERRAR UNA APP
 -CÓMO UTILIZAR LA VIBRACIÓN 
-OBTENER EL ACTUAL VOLUMEN DE AUDIO
 -OBTENER UNA LISTA DE LAS LLAMADAS
 -OBTENER LA RELACIÓN DE SENSORES
 -LISTAR LOS SENSORES DE SISTEMA
 -OBTENER EL TIPO-MIME DE UN FICHERO


CÓDIGO FUENTE:


 - OBTENER EL IMEI

uses
  FMX.Helpers.Android, Androidapi.JNI.JavaTypes,
  Androidapi.JNI.GraphicsContentViewText,
  Androidapi.JNI.Telephony, Androidapi.JNIBridge;
 
var
  TelephonyObj: JObject;
  TelephonyManager: JTelephonyManager;
begin
  TelephonyObj := SharedActivityContext.getSystemService(TJContext.JavaClass.TELEPHONY_SERVICE);
  TelephonyManager := TJTelephonyManager.Wrap((TelephonyObj as ILocalObject).GetObjectID);
  Label1.Text := JStringToString(TelephonyManager.getDeviceId);


- CERRAR UNA APP

uses
  FMX.Platform.Android;
  
procedure TForm1.Button4Click(Sender: TObject);
begin
  MainActivity.finish;
end;


- CÓMO UTILIZAR LA VIBRACIÓN


uses
  FMX.Helpers.Android, Androidapi.JNI.JavaTypes, Androidapi.JNI.Os,
  Androidapi.JNI.App, Androidapi.JNIBridge, Androidapi.JNI.GraphicsContentViewText;
  
function IntArrayToJArray(const OrigArray: array of integer): TJavaArray;
var
  i: integer;
begin
  Result := TJavaArray.Create(Length(OrigArray));
  for i := Low(OrigArray) to High(OrigArray) do
    Result.Items[i] := OrigArray[i];
end;
  
procedure TForm1.Button1Click(Sender: TObject);
var
  VibratorObj: JObject;
  Vibrator: JVibrator;
begin
  VibratorObj := SharedActivity.getSystemService(TJActivity.JavaClass.VIBRATOR_SERVICE);
  Vibrator := TJVibrator.Wrap((VibratorObj as ILocalObject).GetObjectID);
   
    Vibrator.vibrate(StrToInt(ClearingEdit1.Text));
  //or
  //Vibrator.vibrate(IntArrayToJArray([1000, 5000, 3000, 1000]), -1);
  //or
  //Vibrator.cancel();
  //or
  //if Vibrator.hasVibrator() then 
  //... 
  //else 
  //...
end;


- OBTENER EL ACTUAL VOLUMEN DE AUDIO

uses
  Androidapi.JNI.JavaTypes,
  Androidapi.JNI.Media,
  Androidapi.Helpers,
  Androidapi.JNI.App,
  Androidapi.JNIBridge;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  AudioObj: JObject;
  Audio: JAudioManager;
  CurrentVolume: Integer;
begin
  AudioObj = SharedActivity.getSystemService(TJActivity.JavaClass.AUDIO_SERVICE);
  Audio := TJAudioManager.Wrap((AudioObj as ILocalObject).GetObjectID);
  CurrentVolume = Audio.getStreamVolume(TJAudioManager.JavaClass.STREAM_MUSIC);
end;


- OBTENER UNA LISTA DE LAS LLAMADAS DEL MÓVIL


uses
  FMX.Helpers.Android, Androidapi.JNI.GraphicsContentViewText,
  Androidapi.JNI.Provider, Androidapi.JNI.JavaTypes, System.DateUtils;

procedure TForm1.Button1Click(Sender: TObject);
var
  cursor: JCursor;
  CallTypeInt: Integer;
  CallDuration, CallDate: Int64;
  CallName, CallTypeStr: String;
  ListBoxItem: TListBoxItem;
begin

  cursor := SharedActivity.getContentResolver.query(
   TJCallLog_Calls.JavaClass.CONTENT_URI,
    nil,
     nil,
      nil,
       nil);

  ListBox1.Clear;
  ListBox1.BeginUpdate;

  if (cursor.getCount > 0) then
  begin

    while (cursor.moveToNext) do
    begin

      CallName := JStringToString(cursor.getString(
        cursor.getColumnIndex(TJCallLog_Calls.JavaClass.CACHED_NAME)));

      if CallName = '' then begin
        CallName := JStringToString(cursor.getString(
        cursor.getColumnIndex(TJCallLog_Calls.JavaClass.NUMBER)));
      end;

      CallTypeInt := StrToInt(JStringToString(cursor.getString(
        cursor.getColumnIndex(TJCallLog_Calls.JavaClass.&TYPE))));

      case CallTypeInt of
        1: CallTypeStr := 'Bandeja de entrada';
        2: CallTypeStr := 'Bandeja de salida';
        3: CallTypeStr := 'Desconocido';
        else CallTypeStr := 'Cancelado';
      end;

      CallDuration := StrToInt64(JStringToString(cursor.getString(
        cursor.getColumnIndex(TJCallLog_Calls.JavaClass.DURATION))));

      CallDate := StrToInt64(JStringToString(cursor.getString(
        cursor.getColumnIndex(TJCallLog_Calls.JavaClass.DATE))));

      ListBoxItem := TListBoxItem.Create(ListBox1);

      ListBoxItem.ItemData.Text := CallTypeStr + ': ' + CallName;

      ListBoxItem.ItemData.Detail := DateTimeToStr(TTimeZone.Local.ToLocalTime(
        UnixToDateTime(CallDate div 1000))) + '   Duración: '
         + Format('%2.2d:%2.2d', [CallDuration div 60, CallDuration mod 60]);

      ListBox1.AddObject(ListBoxItem);

      Label1.Text := 'Всего: ' + IntToStr(ListBox1.Count);

    end;
  end;

  cursor.close;

  ListBox1.EndUpdate;

end;


- OBTENER LA RELACIÓN DE SENSORES


uses
  android.hardware.SensorManager, android.hardware.Sensor,
  Androidapi.JNI.JavaTypes, FMX.Helpers.android, Androidapi.JNIBridge,
  Androidapi.JNI.GraphicsContentViewText, Androidapi.Helpers;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  SensorsManagerObj: JObject;
  SensorsManager: JSensorManager;
  Sensors: JList;
  Sensor: JSensor;
  i: Integer;
  StringType: String;
begin
  Memo1.Lines.Clear;
 
  SensorsManagerObj := SharedActivityContext.getSystemService
    (TJContext.JavaClass.SENSOR_SERVICE);
  SensorsManager := TJSensorManager.Wrap((SensorsManagerObj as ILocalObject)
    .GetObjectID);
 
  Sensors := SensorsManager.getSensorList(TJSensor.JavaClass.TYPE_ALL);
 
  Label1.Text := 'Found: ' + IntToStr(Sensors.size) + ' sensors';
 
  for i := 0 to Sensors.size - 1 do
  begin
    Sensor := TJSensor.Wrap((Sensors.get(i) as ILocalObject).GetObjectID);
 
    Memo1.Lines.Add('Name: ' + JStringToString(Sensor.getName));
 
    case Sensor.getType of
      TJSensorTYPE_ACCELEROMETER:
        StringType := 'Acelerometro';
      TJSensorTYPE_GRAVITY:
        StringType := 'Gravedad';
      TJSensorTYPE_GYROSCOPE:
        StringType := 'Giroscopio';
      TJSensorTYPE_LIGHT:
        StringType := 'Iluminación';
      TJSensorTYPE_LINEAR_ACCELERATION:
        StringType := 'Aceleración';
      TJSensorTYPE_MAGNETIC_FIELD:
        StringType := 'Campo magnético';
      TJSensorTYPE_ORIENTATION:
        StringType := 'Orientación';
      TJSensorTYPE_PRESSURE:
        StringType := 'Presión';
      TJSensorTYPE_PROXIMITY:
        StringType := 'Proximidad';
      TJSensorTYPE_ROTATION_VECTOR:
        StringType := 'Vector de rotación';
      TJSensorTYPE_TEMPERATURE:
        StringType := 'Temperatura';
    else
      StringType := 'Undefined';
    end;
 
    Memo1.Lines.Add('Type: ' + StringType);
    Memo1.Lines.Add('Vendor: ' + JStringToString(Sensor.getVendor));
    Memo1.Lines.Add('Version: ' + IntToStr(Sensor.getVersion));
    Memo1.Lines.Add('Resolution: ' + FloatToStr(Sensor.getResolution));
    Memo1.Lines.Add('Max Range: ' + FloatToStr(Sensor.getMaximumRange));
    Memo1.Lines.Add('Power: ' + FloatToStr(Sensor.getPower) + 'mA');
    Memo1.Lines.Add('MinDelay: ' + IntToStr(Sensor.getMinDelay));
    Memo1.Lines.Add('----------------');
 
  end;
end;


- LISTAR LOS SENSORES DE SISTEMA

uses
  System.Sensors, System.TypInfo;
 
procedure TForm1.Button2Click(Sender: TObject);
var
  SensorsManager: TSensorManager;
  i: Integer;
  Sensor: TCustomSensor;
begin
  SensorsManager := TSensorManager.Current;
  if SensorsManager.CanActivate then
  begin
    SensorsManager.Activate;
 
    Memo2.Lines.Clear;
    Label2.Text := 'Found: ' + IntToStr(SensorsManager.Count) + ' sensors';
 
    for i := 0 to SensorsManager.Count - 1 do
    begin
      Sensor := SensorsManager.Sensors[i];
      Memo2.Lines.Add('Name: ' + Sensor.Name);
      Memo2.Lines.Add('Category: ' + GetEnumName(TypeInfo(TSensorCategory),
        Ord(Sensor.Category)));
      Memo2.Lines.Add('Manufacturer: ' + Sensor.Manufacturer);
      Memo2.Lines.Add('Model: ' + Sensor.Model);
      Memo2.Lines.Add('State: ' + GetEnumName(TypeInfo(TSensorState),
        Ord(Sensor.State)));
      Memo2.Lines.Add('SerialNo: ' + Sensor.SerialNo);
      Memo2.Lines.Add('Description: ' + Sensor.Description);
      Memo2.Lines.Add('UniqueID: ' + Sensor.UniqueID);
      Memo2.Lines.Add('----------------');
    end;
  end;
  SensorsManager.Current.Deactivate;
end;


- OBTENER EL TIPO-MIME DE UN FICHERO

uses
  Androidapi.JNI.JavaTypes, Androidapi.JNI.Webkit;
  
var
  ExtFile: string;
  mime: JMimeTypeMap;
  ExtToMime: JString;
begin
  //Determinar la extensión de archivo y el tipo MIME
  ExtFile := AnsiLowerCase(StringReplace(TPath.GetExtension(path), '.', '',[]));
  mime := TJMimeTypeMap.JavaClass.getSingleton();
  ExtToMime := mime.getMimeTypeFromExtension(StringToJString(ExtFile));




  Quizás te pueda interesar:

Programación paralela con Delphi 
Obtener datos de los satelites que utiliza el gps de tu dispositivo 
Rotaciones en 3D
-
Obtener la elevación en un punto del mapa 

Un app para mostrar moléculas en 3D

El fin de los archivos ini - Utilizando SaveState con Firemonkey

El título del post no es una afirmación tajante, sino más bien una apreciación personal viendo la  funcionalidad que he encontrado en Delphi, que hasta ahora desconocía y que me ha parecido muy práctica y fácil de utilizar.

Todo gira respecto a la utilidad SaveState

Cuando estamos trabajando con Android si nuestra app está en segundo plano, Android puede decidir que hay cerrarla bajo ciertas condiciones (Low Memory, escasez de recursos del S.O., etc...), cuando esto sucede necesitaríamos guardar ciertos parámetros que permitan restaurar la app en la misma situación en la que estaba antes de cerrarse, es decir es necesario salvar el estado del programa.

Para ello lo que se hace es crear un archivo temporal que se borra cuando la app se restaura, pero si ese archivo lo guardamos en "tpath.GetHomePath" el archivo es permanente hasta que nosotros decidamos suprimirlo.

Este archivo puede contener los siguientes tipo de datos (procede de la clase TBinaryReader, que permite leer tipos de datos desde un stream como valores binarios)

    function ReadBoolean: Boolean; virtual;
    function ReadByte: Byte; virtual;
    function ReadBytes(Count: Integer): TBytes; virtual;
    function ReadChar: Char; virtual;
    function ReadChars(Count: Integer): TCharArray; virtual;
    function ReadDouble: Double; virtual;
    function ReadSByte: ShortInt; inline;
    function ReadShortInt: ShortInt; virtual;
    function ReadSmallInt: SmallInt; virtual;
    function ReadInt16: SmallInt; inline;
    function ReadInteger: Integer; virtual;
    function ReadInt32: Integer; inline;
    function ReadInt64: Int64; virtual;
    function ReadSingle: Single; virtual;
    function ReadString: string; virtual;
    function ReadWord: Word; virtual;
    function ReadUInt16: Word; inline;
    function ReadCardinal: Cardinal; virtual;
    function ReadUInt32: Cardinal; inline;
    function ReadUInt64: UInt64; virtual;


Por ejemplo, para leer un parámetro del tipo Boolean de nuestra app, escribiríamos:

...
VAR
  Reader: TBinaryReader;
...
Parametro1:=Reader.ReadBoolean;
...

Y para almacenarlo:



...
VAR
  Writer: TBinaryWriter;
...
Writer.Write(Parametro1);



En la propiedad "StoragePath"  especificamos el Path para almacenar el estado, si no indicamos nada el estado se perderá al reiniciar la app, en caso contrario el estado se grabará en un fichero, en este caso le he llamado "MIARCHIVODEDATOS.tmp".

El procedimiento para la lectura de parámetros sería el siguiente: (yo lo suelo poner en el ONCREATE del form)

PROCEDURE Tform1.LeerArchivoParametros;
VAR
  Reader: TBinaryReader;

BEGIN
  SaveState.Name := 'MIARCHIVODEDATOS.tmp';
// IMPORTANTE al poner tpath.GetHomePath se crea un archivo permanente
// que no se borra cuando se restaura la app

  SaveState.StoragePath := tpath.GetHomePath;

  IF SaveState.Stream.Size > 0 THEN
  BEGIN
    Reader := TBinaryReader.Create(SaveState.Stream);

    TRY

      Parametro1 := Reader.ReadBoolean;
      Parametro2 := Reader.ReadInteger;
      Parametro3 := Reader.ReadString;

    FINALLY
      Reader.Free;
    END;

  END;

END;

y el procedimiento de escritura sería en el evento "ONSAVESTATE" del form de esta manera:

PROCEDURE Tform1.FormSaveState(Sender: TObject);

VAR
  Writer: TBinaryWriter;

BEGIN
  SaveState.Stream.Clear;

  Writer := TBinaryWriter.Create(SaveState.Stream);

  TRY
    Writer.Write(Parametro1);

    Writer.Write(Parametro2);

    Writer.Write(Parametro3);

  FINALLY
    Writer.Free;
  END;
END;


Lo que me ha parecido interesante de esta forma de almacenar los parámetros de una app es que según las pruebas que he hecho, funciona para cualquier S.O., Android, IOS y Windows.
Recordad que si lo usáis en Android, para que funcione bien, previamente tendréis que desinstalar las versiones previas de la app.


Espero que os haya sido útil, 

...hasta el próximo post...