10 Ekim 2018 Çarşamba

Adding custom gestures to Xamarin.Forms VisualElements with Renderer


Tek yapmak istediğim label'ıma uzun basılı tuttuktan sonra içindeki text'in kopyalanmasıydı. Haliyle gesture'lara baktım ancak xamarin.forms içinde hali hazırda "longtap" gibi bir gesturerecognizer olmadığını gördüm..

Haliyle ben de "LAYN!" deyip özel, uzun basılabiliritesi olan bir label yaptım. :) Güzel örneği bulunca hepsini ekleyesim de geldi ama sonra dedim boşver mono, zaten örnek var starla geç.
Hemen unutmadan kaynağı ekleyeyim;


Forms;
using System;
using Xamarin.Forms;

namespace GestureRendererSample
{
    public class MyLabel : Label
    {
        public MyLabel()
        {
        }
        public Action OnLongTap { get; set; }
    }
}

Android;

LabelRenderer;
using System;
using Android.Content;
using Android.Views;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(MyLabel), typeof(MyLabelRenderer))]
namespace GestureRendererSample.Droid
{
    public class MyLabelRenderer : LabelRenderer
    {
        private readonly GestureListener _listener;
        private readonly GestureDetector _detector;

        public MALabelRenderer(Context context) : base(context)
        {
            _listener = new GestureListener();
            _detector = new GestureDetector(context, _listener);
            #region LongPressedtoLabel
            //Bu kısım biraz eksik kaldı maalesef acele iş fln durumları. En hızlı çözümü messaging center ile buldum, tabii ki geçici :)
            MessagingCenter.Unsubscribe<string>(string.Empty, "LongPressedtoLabel");
            MessagingCenter.Subscribe<string>(string.Empty, "LongPressedtoLabel",
            (sender) =>
            {
                try
                {
                    var control = ((MALabel)this.Element);
                    if (control?.OnLongTap != null)
                    {
                        control.OnLongTap();
                    }
                }
                catch (Exception ex)
                {
                    LogHelper.LogError(ex);
                }
            });

            #endregion
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement == null)
            {
                this.GenericMotion -= HandleGenericMotion;
                this.Touch -= HandleTouch;
            }

            if (e.OldElement == null)
            {
                this.GenericMotion += HandleGenericMotion;
                this.Touch += HandleTouch;
            }
        }

        void HandleTouch(object sender, TouchEventArgs e)
        {
            _detector.OnTouchEvent(e.Event);
        }

        void HandleGenericMotion(object sender, GenericMotionEventArgs e)
        {
            _detector.OnTouchEvent(e.Event);
        }
    }
}


GestureListener;
using System;
using Android.Views;

namespace GestureRendererSample.Droid
{
public class GestureListener : GestureDetector.SimpleOnGestureListener
{
public override void OnLongPress(MotionEvent e)
{
MessagingCenter.Send(string.Empty, "LongPressedtoLabel");
base.OnLongPress(e);
}

public override bool OnDoubleTap(MotionEvent e)
{
return base.OnDoubleTap(e);
}

public override bool OnDoubleTapEvent(MotionEvent e)
{
return base.OnDoubleTapEvent(e);
}

public override bool OnSingleTapUp(MotionEvent e)
{
return base.OnSingleTapUp(e);
}

public override bool OnDown(MotionEvent e)
{
return base.OnDown(e);
}

public override bool OnFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
return base.OnFling(e1, e2, velocityX, velocityY);
}

public override bool OnScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
{
return base.OnScroll(e1, e2, distanceX, distanceY);
}

public override void OnShowPress(MotionEvent e)
{
base.OnShowPress(e);
}

public override bool OnSingleTapConfirmed(MotionEvent e)
{
return base.OnSingleTapConfirmed(e);
}
}
}


iOS;
using System;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(MyLabel), typeof(MyLabelRenderer))]
namespace GestureRendererSample.iOS
{
    public class MyLabelRenderer : LabelRenderer
    {
        UILongPressGestureRecognizer longPressGestureRecognizer;

//UIPinchGestureRecognizer pinchGestureRecognizer;
//UIPanGestureRecognizer panGestureRecognizer;
//UISwipeGestureRecognizer swipeGestureRecognizer;
//UIRotationGestureRecognizer rotationGestureRecognizer;

        public MyLabelRenderer()
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
        {
            base.OnElementChanged(e);

            longPressGestureRecognizer = new UILongPressGestureRecognizer(() => {
                var control = ((MyLabel)this.Element);
                if (control?.OnLongTap != null)
                {
                    control.OnLongTap();
                }
            });

//pinchGestureRecognizer = new UIPinchGestureRecognizer(() => Console.WriteLine("Pinch"));
//panGestureRecognizer = new UIPanGestureRecognizer(() => Console.WriteLine("Pan"));
//swipeGestureRecognizer = new UISwipeGestureRecognizer(() => Console.WriteLine("Swipe"));
//rotationGestureRecognizer = new UIRotationGestureRecognizer(() => Console.WriteLine("Rotation"));

            if (e.NewElement == null)
            {
                if (longPressGestureRecognizer != null)
                {
                    this.RemoveGestureRecognizer(longPressGestureRecognizer);
                }

//if (pinchGestureRecognizer != null)
//{
// this.RemoveGestureRecognizer(pinchGestureRecognizer);
//}
//if (panGestureRecognizer != null)
//{
// this.RemoveGestureRecognizer(panGestureRecognizer);
//}
//if (swipeGestureRecognizer != null)
//{
// this.RemoveGestureRecognizer(swipeGestureRecognizer);
//}
//if (rotationGestureRecognizer != null)
//{
// this.RemoveGestureRecognizer(rotationGestureRecognizer);
//}
            }

            if (e.OldElement == null)
            {
                this.AddGestureRecognizer(longPressGestureRecognizer);

//this.AddGestureRecognizer(pinchGestureRecognizer);
//this.AddGestureRecognizer(panGestureRecognizer);
//this.AddGestureRecognizer(swipeGestureRecognizer);
//this.AddGestureRecognizer(rotationGestureRecognizer);
            }
        }
    }
}


Bu da buracıkta duruversin gari


adb komutlarını macos'da kullanmak


Hali hazırda mac'imizde adb yüklü olmasına rağmen böyle bir komut olmadığına dair hata alırsak yapmamız gerekenler; 

Terminal'de fish'in config file'ına girilir. (ben fish kullandığımdan dolayı, diğer shell'ler için onların config file'ına girilmesi gerek)

nano ~/.config/fish/config.fish 

Bu dosyaya aşağıdaki ayar nakşedilir. (Benim adb'min yolu bu)

set -x PATH $HOME/Library/Developer/Xamarin/android-sdk-macosx/platform-tools $$

Ardından fishimizi yenileyelim

source ~/.config/fish/config.fish


Bash'te ise şu komutlar iş görüyor;

Terminal'de bash'e geçip şu komutları çalıştırmalıyız.

export PATH="/Users/monolara/Library/Developer/Xamarin/android-sdk-macosx/platform-tools:$PATH"
export PATH="/Users/monolara/Library/Developer/Xamarin/android-sdk-macosx/tools:$PATH"


Bu işlemlerden sonra adb komutlarını tüm dizinlerden çalıştırabiliyor olmamız lazım.
adb devices -l
adb -s <deviceid> uninstall <packageid>
adb -s <deviceid> shell pm list packages

gibi..

Üsttekileri yapmazsak platform-tools klasörüne gidip komutları şöyle çalıştırabiliriz;
./adb devices
./adb -s <deviceid> uninstall <packageid>

https://www.androidcentral.com/installing-android-sdk-windows-mac-and-linux-tutorial

8 Ekim 2018 Pazartesi

Jenkins ve GitLab ile uçtan uca otomasyon



Bugün çok eğlenceli bir geliştirme fırsatı verilmiş olmanın hüsnü-sevinci içerisindeyim :)) 

Coder'lıktan öteye gidip orchestration ve otomasyon yaptım biraz ve gideri varmış bu işlerin de hani. :P

Öncelikle işi özetleyeyim, yapmak istediğimiz şey projeyi git'e push'ladığımız zaman telegram botumuza bir adet onay notification'ı gelsiiin, onay verirsek proje otomatik olarak yayınlansıın. 

Bu işlemleri yapmak için bir sürü şey kullandık umarım fazla karıştırmam :)

Öncelikle otomasyon için meşhur Jenkins'i kullandık. Jenkins adımlarını belirlemek için Jenkinsfile diye bir file'ımız var, bu file'ı "groovy" dili ile işlememiz gerekiyor. Projenin içine de koyulabiliyor jenkins ayarlarından da yönetilebiliyor. Ana taslağı şu şekıl;


def swarmWebhookUrl = "https://swarm.adresimiz.com/api/webhooks/......"
def monopushUrl = "https://webhook.monopush.io/telegram/....."
node {
  stage('SCM') {
    git credentialsId'GitLabSSH'url'[gitadresi.git]'
  }
  stage('Docker Build') {
    sh 'docker build -t [...] -f bot.Dockerfile .'
  }
  stage('Docker Push') {
    withDockerRegistry(credentialsId'DockerCredentials'url'[https://pushlanacakadresimiz]') {
      sh 'docker tag [...] [...]:build-$BUILD_NUMBER'
      sh 'docker tag [...] [...]'
      sh 'docker push [...]:build-$BUILD_NUMBER'
      sh 'docker push [...]'
    }
  }
  stage('Approvement for Deploy'){
// 15 dk bekle, eğer olumlu cevap alırsan bir sonraki adıma geç
    timeout(time15unit'MINUTES'){
      hook = registerWebhook()
      def hookUrl = hook.getURL()
      def message = """
      {
          "message": "Project ready for deployment. I will deploy it to production if you want.",
          "action": "Do you want me to do that?",
          "buttons": [
              {
                  "id": "APPROVE",
                  "text": "Yes, Please",
                  "webhook": "${hookUrl}",
                  "success_message": "\\uD83D\\uDC4D Done. I did it.",
                  "error_message": "Something is wrong with your request."
              },
              {
                  "id": "CANCEL",
                  "text": "No",
                  "success_message": "\\uD83D\\uDD96 OK, I have canceled it."
              }
          ]
      }
      """
      httpRequest url: monopushUrl, httpMode'POST'requestBody: message, contentType'APPLICATION_JSON'
      echo "Waiting for POST to ${hookUrl}"
      data = waitForWebhook hook
      echo "Webhook called with data: ${data}"
    }
  }
  stage('Deploy') {   
      httpRequest url: swarmWebhookUrl, httpMode'POST'
  }
}


Bu Jenkinsfile'da 5 adım belirlemişiz ve bunlar stage'lerle belirtilmiş. Stage'lerin içinde yazan isimleri başlık olarak Jenkins'de görüyoruz.

Şimdi adımları özetlersek eğer, ilk adım olarak yani SCM adımında projeyi gitlab'dan çekiyoruz. 
2. ve 3. adımlar docker adımları yani bu adımlarda projeyi dockerize edip pushlama işlemi yaptık. 
4. adım approvement kısmı. Yani deploy edilsin mi edilmesin mi kararımızın verildiği kısım. Bu adımda kullandığımız timeout cevabı bekleyeceğimiz zamanı gösteriyor, eğer bu süre aksiyon alınmadan geçerse deploy işlemi yapılmıyor. 
Bu adımda monopush devreye giriyor. Yani telegram botumuza approvement mesajı atma kısmı. MonoPush'un ne olduğu ve nasıl kullanıldığı şu makalelerden görülebilir;
https://monopush.io/
https://www.selcukermaya.com/tr/bir-problem-45-dk-bir-proje-bir-makale/

Ve en son adım da deployment işlemi.. 

Tabii ki bu işlemlerden sonra gitlab ile jenkins'i ve swarm'ı tanıştırmamız lazım :) "Haadi dalalım"

GitLab'da projemizin integrations kısmına girip jenkins'den aldığımız adresi ve token'ı yazıp webhook ekle diyoruz.



Token bilgisini Jenkins'den, build triggers başlığından Advanced butonuna tıklayarak alabiliriz.


Tabi swarm Jenkinsfile'da yazan swarm webhookumuzu da swarm'dan almamız gerekiyor. Bu kısmı da atlamayalım. 

Sonuç olarak artık "git push" komutumuzla telegramımızda istediğimiz gruba bir notification gelecek, bizden onay alacak ve deploy işlemini gerçekleştirecek. Ve biz dee Jenkins'den izlemenin keyfini süreceğiz. İşte şu şekıl;



Fazla huzurlu.. :S

19 Eylül 2018 Çarşamba

Xamarin.iOS handling notifications at foreground or background and reaching the custom data in it


İngilizce başlıktan sonra abesle iştigal edip yazımı türkçe yazacağım. :)

Bir süredir notification'larla boğuşuyorum. Boğuşmamın nedeni ise sürekli değişen bir altyapı olmuş sanırım ve birr dolu farklı dökümantasyondaki yolları izleyip denemem gerekti. Temiz ve düzenli bilgiye maalesef ulaşamadım, zaten örnek de fazla bulabilmiş değilim.

Her neyse deneye yanıla ios kısmındaki notification yönetimini şu şekilde yaptım;

AppDelegate class'ındaki FinishedLaunching'e aşağıdaki gibi kodlar nakşedilir. Bu kısım foreground yani uygulama açıkken çalışacak kısmı ifade ediyor.



public
 override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            Forms.Init();
// … kodlar kodlar … //
// … kodlar kodlar … //

            if (UIDevice.CurrentDevice.CheckSystemVersion(80))
            {
                var notificationSettings = UIUserNotificationSettings.GetSettingsForTypes(
                    UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound, null
                );

                app.RegisterUserNotificationSettings(notificationSettings);
            }

            // Foreground notifications için.
            UNUserNotificationCenter.Current.Delegate = new UserNotificationCenterDelegate();


            return base.FinishedLaunching(app, options);
        }





Nedir buradaki UserNotificationCenterDelegate() ? UNUserNotificationCenter'dan türetip metodlarını override ettiğimiz bir class. İşte şöyle;


public class UserNotificationCenterDelegate : UNUserNotificationCenterDelegate
    {

#region Override Methods
        public override void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
        {   
              // custom data böyle alınıyormuş.. bunu bir türlü bulamamıştım.      
            var customData = response.Notification.Request.Content.UserInfo;

            
// … kodlar kodlar … //
            // … kodlar kodlar … //

            switch (response.ActionIdentifier)
            {
                    // Bu actionlar custom actionlar
                    // Yani notification’a buton eklediyseniz butonun id'sine göre buralar çalışıyor.
                    //Bu ayrı bir yazı konusu. :) 
                case "approve":
                    
// … kodlar kodlar … //
                    break;
                case "decline":
                    
// … kodlar kodlar … //
                    break;
                default:
                    // Bu actionlar default actionlar
                    // Yani notification’ın üzerine tıklandığında burası çalışıyor.
                    //haliyle custom datayı en çok burada kullandım.
                    if (response.IsDefaultAction)
                    {
                        if (_notificationCustomData != null)
                        {
                            
// … custom data ile dans eden kodlar … //
                        }
                    }
                    else if (response.IsDismissAction)
                    {
                        // Buraya düşüremedim uğraşmadım da gerçi
J
                    }
                    break;
            }
            // Inform caller it has been handled
            completionHandler();
        }

        public override void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
        {
            try
            {
                
// … kodlar kodlar … //
                // burada da custom data böyle alınıyormuş..
                var customData = notification.Request.Content.UserInfo;
               
                if (customData != null)
                {
                    
// … kodlar kodlar … //
                }else{
                
          // uygulama açıkken de isterseniz üstten notification gösterebilirsiniz, local notification oluşturabilirsiniz vs. tüm daha elinizde.
          // bu alert kısmı None olsa idi hiç notification göstermemiş olacaktık.
     completionHandler(UNNotificationPresentationOptions.Alert);
                    }
                }                
            }
            catch (Exception ex)
            {               
                System.Diagnostics.Debug.WriteLine(ex);
            }
        }
}



Bu arada AppCenter'ı kullandık notification'lar için. İnşallah örnek bir github projesi de oluşturmak istiyorum, o zaman daha kapsamlı bir yazı yazmak isterim nasipse kısmetseee :P

Şimdilik sadecee blogun yazarından bir mum duruşuuu :D

(Burada bir karikatür vardı ama telif yerim diye kaldırdım. Herkese dava açıyorlarmış karikatür paylaşılıyor diye. Karikatürün doğasına hiç uymuyor bence bu tavır :-/ )

Hay Allah :S