Sigurd Snørteland


tweet search – one code, three mobile platforms (wp7, monodroid, monotouch)

Here is the presentation and the source code from the speech I held at the Norwegian .Net User Group last wednesday.

tweet search
‘tweet search’ is a mobile twitter search app written in c# for Windows Phone 7, iPhone and Android. All three apps share the same twitter search code, but they have custom gui code (WP7=Silverlight, iPhone=MonoTouch, Android=MonoDroid).

Go to the bottom of this article to find links to the source code & presentation.

Shared twitter-code:

public class Twitter
    {
        public event EventHandler twitteSearchCompleted;

        public Twitter()
        {
        }

        public void search(string searchText)
        {
            if (searchText != "")
            {
                WebClient client = new WebClient();
                client.DownloadStringAsync(new Uri("http://search.twitter.com/search.atom?q=" + searchText));
                client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
            }
        }

        private void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                List twitteList = new List();

                XDocument xml = XDocument.Parse(e.Result);
                XNamespace atomNS = "http://www.w3.org/2005/Atom";

                var tempList = (from entry in xml.Descendants(atomNS + "entry")
                                select new TwitterObject()
                                {
                                    ID = entry.Element(atomNS + "id").Value,
                                    Title = entry.Element(atomNS + "title").Value,
                                    Date = DateTime.Parse(entry.Element(atomNS + "published").Value),
                                    AuthorName = entry.Descendants(atomNS + "author").Elements(atomNS + "name").FirstOrDefault().Value,
                                    AuthorUri = entry.Descendants(atomNS + "author").Elements(atomNS + "uri").FirstOrDefault().Value,
                                    AuthorImage = (from imgElement in entry.Elements(atomNS + "link")
                                                   where imgElement.Attribute("rel") != null
                                                   && imgElement.Attribute("rel").Value.Contains("image")
                                                   && imgElement.Attribute("href") != null
                                                   select imgElement.Attribute("href").Value).FirstOrDefault()
                                }).ToList();

                twitteList = tempList.ToList();
                twitteSearchCompleted(twitteList, null);
            }
        }
    }

    public class TwitterObject
    {
        public string ID { get; set; }
        public string Title { get; set; }
        public DateTime Date { get; set; }
        public string AuthorName { get; set; }
        public string AuthorUri { get; set; }
        public string AuthorImage { get; set; }
    }

WP7 code:

namespace tweet_search_wp7
{
    public partial class MainPage : PhoneApplicationPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        #region UI

        private void txtSearch_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                this.Focus();
            }
        }

        private void txtSearch_LostFocus(object sender, RoutedEventArgs e)
        {
            search();
        }

        private void lboTwitte_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            lboTwitte.SelectedIndex = -1;
        }

        private void imgSearch_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            search();
        }

        #endregion

        #region Twitter

        private void search()
        {
            lblLoading.Visibility = Visibility.Visible;
            lboTwitte.Opacity = 0.3;

            Twitter twitter = new Twitter();
            twitter.twitteSearchCompleted += new EventHandler(twitter_downloadCompleted);
            twitter.search(txtSearch.Text);
        }

        private void twitter_downloadCompleted(object sender, EventArgs e)
        {
            List list = (List)sender;
            lboTwitte.ItemsSource = list;

            lblLoading.Visibility = Visibility.Collapsed;
            lboTwitte.Opacity = 1;
        }

        #endregion
    }
}

MonoTouch code:

namespace tweetsearchiphone
{
	public partial class MainPage : UIViewController
	{
		#region Constructors

		// The IntPtr and initWithCoder constructors are required for items that need
		// to be able to be created from a xib rather than from managed code

		public MainPage (IntPtr handle) : base(handle)
		{
			Initialize ();
		}

		[Export("initWithCoder:")]
		public MainPage (NSCoder coder) : base(coder)
		{
			Initialize ();
		}

		public MainPage () : base("MainPage", null)
		{
			Initialize ();
		}

		void Initialize ()
		{
		}

		#endregion

		#region UI

		public override void ViewDidLoad()
		{
			base.ViewDidLoad();

			lblTwitter.Text = "";
			txtSearch.ShouldReturn = delegate
			{
				txtSearch.ResignFirstResponder();
				search();

				return true;
			};

			imgSearch.TouchUpInside += HandleImgSearchTouchUpInside;
		}

		void HandleImgSearchTouchUpInside (object sender, EventArgs e)
		{
			search();
		}

		#endregion

		#region Twitter

		private void search()
		{
			Twitter twitter = new Twitter();
			twitter.twitteSearchCompleted += twitter_downloadCompleted;
			twitter.search(txtSearch.Text);
		}

		void twitter_downloadCompleted (object sender, EventArgs e)
		{
			List list = (List) sender;
			BeginInvokeOnMainThread (delegate {

				foreach(TwitterObject tweet in list)
				{
		        	lblTwitter.Text += tweet.Date.ToString() + Environment.NewLine + tweet.Title + Environment.NewLine + Environment.NewLine;
				}

		    });
		}

		#endregion
	}
}

MonoDroid code:

namespace tweet_search_android
{
	[Activity (Label = "tweet search", MainLauncher = true)]
	public class ButtonActivity : Activity
	{
        List list = new List();
        Android.Widget.Button btnSearch;
        TextView lblTwitter;
        EditText txtSearch;

        private List photo_ids = new List() { Resource.drawable.bird };

		protected override void OnCreate (Bundle bundle)
		{
			base.OnCreate (bundle);
            SetContentView(Resource.layout.main);

            ImageView btnSearch = FindViewById(Resource.id.btnSearch);
            btnSearch.SetImageResource(photo_ids[0]);
            btnSearch.Click += new EventHandler(btnSearch_Click);

            txtSearch = FindViewById(Resource.id.txtSearch);
            lblTwitter = FindViewById(Resource.id.lblTwitter);
		}

        void btnSearch_Click(object sender, EventArgs e)
        {
            if (list.Count == 0)
            {
                Twitter twitter = new Twitter();
                twitter.twitteSearchCompleted += new EventHandler(twitter_twitteSearchCompleted);
                twitter.search("nnug");
            }
            else
            {
                foreach (TwitterObject twitter in list)
                {
                    lblTwitter.Text += twitter.Date.ToString() + System.Environment.NewLine + twitter.Title + System.Environment.NewLine + System.Environment.NewLine;
                }

                list = new List();
            }
        }

        void twitter_twitteSearchCompleted(object sender, EventArgs e)
        {
            list = (List)sender;
        }
	}
}

Advertisements

loud tweets – a “twitter-to-speak” wp7 app (source code included)

‘loud tweets’ is a wp7 twitter app. You type in a twitter search keyword and the app refreshes the search every 60 seconds. All recived tweets will automatically be translated to english and spoken out loud by the phone.

I found inspiration for making this app in a Tim Heuer article ‘Make your Silverlight applications Speak to you with Microsoft Translator’

I haven’t had the chance to test this app on a wp7 device yet, but it works nice in the emulator 🙂

Go to the bottom off this page to find a link to the source code.

Architecture
  Be aware of this comment from Michael Washington
Cool it works on mt HTC HD7. Note, you just cant hear anything while debugging. This url explains how to debug using media: http://blogs.msdn.com/b/jaimer/archive/2010/11/03/tips-for-debugging-wp7-media-apps-with-wpconnect.aspx

The ‘System.Speech’ library is not included in WP7/SL, but ‘text-to-speek’ can be done using the Speak-service in the Microsoft Bing Traslation API. You can send a string to Bing and get a sound-file back as a byte[]. You can’t play the the file directe (it’s not supported) and insted you have to save it to isolated storage and than load it and send it to a mediaelement-control.

The code requires a Bing Id, something you can get for free from this site: http://www.microsofttranslator.com/dev. Copy your Bing ID into the string ‘_appid’.

public partial class MainPage : PhoneApplicationPage
    {
        ...
        string _appId = "";
    }

The twitter search starts when the search textfield lose it’s focus and when you click on the bird image.

        private void txtTwitte_LostFocus(object sender, RoutedEventArgs e)
        {
            TwitterTimer();
        }

        private void imgTwitter_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            TwitterTimer();
        }

        private void TwitterTimer()
        {
            if (this.timelineTimer == null)
            {
                this.timelineTimer = new DispatcherTimer();
                this.timelineTimer.Tick += new EventHandler(TwitterTimer_Tick);
                this.timelineTimer.Interval = new TimeSpan(0, 1, 0);
                this.timelineTimer.Start();
            }

            this.TwitterTimer_Tick(this, EventArgs.Empty);
        }

        void TwitterTimer_Tick(object sender, EventArgs e)
        {
            twitter();
        }

This is the twitter search code:

        private void twitter()
        {
            try
            {
                if (txtTwitte.Text != "")
                {
                    lblLoading.Visibility = Visibility.Visible;
                    lboTwitte.Opacity = 0.3;

                    WebClient client = new WebClient();
                    client.DownloadStringAsync(new Uri(twitterUrl + txtTwitte.Text));
                    client.DownloadStringCompleted += ((s, e) =>
                    {
                        if (e.Error == null)
                        {
                            XDocument xml = XDocument.Parse(e.Result);
                            XNamespace atomNS = "http://www.w3.org/2005/Atom";

                            var tempList = (from entry in xml.Descendants(atomNS + "entry")
                                            select new TwitterObject()
                                            {
                                                ID = entry.Element(atomNS + "id").Value,
                                                Title = entry.Element(atomNS + "title").Value,
                                                Date = DateTime.Parse(entry.Element(atomNS + "published").Value),
                                                AuthorName = entry.Descendants(atomNS + "author").Elements(atomNS + "name").FirstOrDefault().Value,
                                                AuthorUri = entry.Descendants(atomNS + "author").Elements(atomNS + "uri").FirstOrDefault().Value,
                                                AuthorImage = (from imgElement in entry.Elements(atomNS + "link")
                                                               where imgElement.Attribute("rel") != null
                                                               && imgElement.Attribute("rel").Value.Contains("image")
                                                               && imgElement.Attribute("href") != null
                                                               select imgElement.Attribute("href").Value).FirstOrDefault()
                                            }).ToList();

                            twitteList = tempList.ToList();
                            lboTwitte.ItemsSource = twitteList;
                            lblLoading.Visibility = Visibility.Collapsed;
                            lboTwitte.Opacity = 1;

                            tweetLoud();
                        }
                    });
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

The method ‘tweetLoud()’ gets called when a result is received from twitter. This method keeps track on which tweets that’s already been “spoken out loud”. When it finds a new tweet it calls the ‘translate()’ method.

        private void tweetLoud()
        {
            if (soundOn)
            {
                for (int i = 0; i < lboTwitte.Items.Count; i++)
                {
                    TwitterObject twitterObject = (TwitterObject)lboTwitte.Items[i];

                    if(!tweetLoudList.Contains(twitterObject.Title))
                    {
                        tweetLoudList.Add(twitterObject.Title);
                        translate(twitterObject.Title);
                        break;
                    }
                }
            }
        }

Code for translating the tweet to english using the Bing Translate API:

        private void translate(string text)
        {
            try
            {
                if (soundOn)
                {
                    text = removeHtml(text);
                    WebClient client = new WebClient();
                    client.OpenReadCompleted += new OpenReadCompletedEventHandler(translate_Completed);
                    client.OpenReadAsync(new Uri(string.Format(translateUrl, _appId, "en", HttpUtility.UrlEncode(text))));
                }
            }
            catch (Exception ex)
            {
                tweetLoud();
                MessageBox.Show(ex.ToString());
            }
        }

        void translate_Completed(object sender, OpenReadCompletedEventArgs e)
        {
            try{
                if (e.Error == null)
                {
                    DataContractSerializer data = new DataContractSerializer(typeof(string));
                    string response = data.ReadObject(e.Result) as string;
                    speek(response);
                }
                else
                {
                    tweetLoud();
                }
            }
            catch (Exception ex)
            {
                tweetLoud();
                MessageBox.Show(ex.ToString());
            }
        }

This code recives a byte[] file from Bing, saves the file to isolated storage and plays the file in the medielement control.

        private void speek(string text)
        {
            try
            {
                if (soundOn)
                {
                    WebClient client = new WebClient();
                    client.OpenReadCompleted += new OpenReadCompletedEventHandler(speek_Completed);
                    client.OpenReadAsync(new Uri(string.Format(speakUrl, _appId, text, "en")));
                }
            }
            catch (Exception ex)
            {
                tweetLoud();
                MessageBox.Show(ex.ToString());
            }
        }

        void speek_Completed(object sender, OpenReadCompletedEventArgs e)
        {
            try
            {
                if (e.Error == null)
                {
                    var sound = e.Result;

                    media.Source = null;
                    string filename = "twitter";
                    using (IsolatedStorageFile userStoreForApplication = IsolatedStorageFile.GetUserStoreForApplication())
                    {
                        bool fileExists = userStoreForApplication.FileExists(filename);

                        if (fileExists)
                        {
                            userStoreForApplication.DeleteFile(filename);
                        }

                        var isolatedStorageFileStream = userStoreForApplication.CreateFile(filename);

                        using (isolatedStorageFileStream)
                        {
                            SaveFile(e.Result, isolatedStorageFileStream);

                            if (e.Error == null)
                            {
                                media.SetSource(isolatedStorageFileStream);
                                media.Play();
                                media.MediaEnded += new RoutedEventHandler(media_MediaEnded);
                                //tweetLoud();
                            }
                        }
                    }
                }
                else
                {
                    tweetLoud();
                }
            }
            catch (Exception ex)
`           {
                tweetLoud();
                MessageBox.Show(ex.ToString());
            }
        }

You can’t play the the file directe (it’s not supported) and insted you have to save it to isolated storage using this code:

        public static void SaveFile(Stream input, Stream output)
        {
            try
            {
                byte[] buffer = new byte[32768];
                while (true)
                {
                    int read = input.Read(buffer, 0, buffer.Length);
                    if (read <= 0)
                    {
                        return;
                    }
                    output.Write(buffer, 0, read);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

When the mediaelement is done playing a tweet sound file it calls the method ‘tweetLoud()’ to get the next tweet translated and played.

        void media_MediaEnded(object sender, RoutedEventArgs e)
        {
            try
            {
                tweetLoud();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

Download the source code:


Tetris7 – a wp7 game (source code included)

I have converted  another “normal” silverlight app to WP7. This time it’s a tetris-game (YYTetris) from YYGames. I have updated the user interface a bit and added a top score feature.

Go to the bottom of this page to find a link to the source code.

Screenshot of the original YYTetris-game:

Download the source code:


myChannel9 & wallstreet now on silverlight.net

Microsoft has now added  myChannel9 & wallstreet as ‘Community samples’-apps on their site www.silverlight.net.

myChannel9

Url: http://www.silverlight.net/community/samples/silverlight-samples/mychannel9-wp7-app-36865

wallstreet

Url: http://www.silverlight.net/community/samples/silverlight-samples/wallstreet-wp7-app-36924


How I made the ‘myChannel9’ wp7 app (source code included) – Part 1

‘myChannel9’ is a Windows Phone 7 app for streaming of Channel9 content. It offers features like the ability to favorite a show, browse by tags or search by keyword and, of course, the ability to watch videos right on your device (in portrait and landscape mode). I participated in the Norwegian WP7 developer contest with ‘myChannel9’ and I was one of three lucky winners! The ‘myChannel9′ app is now available as a free app in the WP7 Marketplace, and the source code is available for download from codeplex (mychannel9.codeplex.com). I hope that you take a look at the code and helps making this a better Channel9-app!

Go to the bottom off this article to find a link to the source code.

The user experience
The main user experience is build up around the wp7 panorama control and consists of these 5 “blocks”:
● news
● shows
● tags (not visible in the wp7 marketplace version)
● favorites
● search

The ‘MainPage’ xaml looks like this:

<Grid x:Name="LayoutRoot" Background="Transparent">

        <controls:Panorama Title="  my channel 9">
            <controls:Panorama.Background>
                <ImageBrush ImageSource="PanoramaBackground.png"/>
            </controls:Panorama.Background>

            <controls:PanoramaItem Header="news">
                <Grid>
                    <local:Control_News x:Name="controlNews" />
                    <Image x:Name="btnSettings" Source="Images/logo.png" HorizontalAlignment="Left" VerticalAlignment="Top" Width="150" Height="150" Margin="-20,-290,0,0" MouseLeftButtonDown="btnSettings_MouseLeftButtonDown" />
                    <Image x:Name="btnRefresh" Source="Images/appbar_refresh.png" HorizontalAlignment="Right" VerticalAlignment="Top" Width="48" Height="48" Margin="0,-85,20,0" MouseLeftButtonDown="btnRefresh_MouseLeftButtonDown" />
                </Grid>
            </controls:PanoramaItem>

            <controls:PanoramaItem Header="shows" >
                <local:Control_Show x:Name="controlShow" />
            </controls:PanoramaItem>

            <controls:PanoramaItem Header="tags" >
                <local:Control_Tags x:Name="controlTags" />
            </controls:PanoramaItem>

            <controls:PanoramaItem Header="favorites" >
                <local:Control_Favorites x:Name="controlFavorites" />
            </controls:PanoramaItem>

            <controls:PanoramaItem Header="search" >
                <local:Control_Search x:Name="controlSearch" />
            </controls:PanoramaItem>

        </controls:Panorama>
    </Grid>

As you can see I use ‘user controls’ inside the panorama “blocks” to avoid to much code & xaml in the MainPage. I think this works great, except that it “destroys the “navigation-route”. If you navigate like this: this.NavigationService.Navigate(new Uri(“/Show.xaml”, UriKind.Relative)); inside a ‘user control’ then the ‘back’-button wont work correct. To fix this i use a event like this:

public event EventHandler goToVideoInfo'

inside the ‘user control’ and raise it like this:

goToVideoInfo(video, new EventArgs());

In the ‘MainPage’ I hook up to the event inside the ‘user control’ like this:

        public MainPage()
        {
            InitializeComponent();

            controlShow.goToShow += new EventHandler(controlShow_goToShow);
            ...
        }

and the method looks like this:

void controlShow_goToShow(object sender, EventArgs e)
        {
            this.NavigationService.Navigate(new Uri(/VideoInfo.xaml, UriKind.Relative));
        }

The ‘News’ page

The news page is basically just a RSS reader that is showing the main Channel9-feed. I use linq to “convert” the feed to ‘VideoObjects’, add them to a list, and then bind the list to a listbox like this:

        public void loadNews()
        {
            lblLoading.Visibility = Visibility.Visible;
            lboNews.Visibility = Visibility.Collapsed;

            WebClient client = new WebClient();
            client.DownloadStringCompleted += ReadNews;
            string url = "http://channel9.msdn.com/Feeds/RSS/";
            client.DownloadStringAsync(new Uri((url)));
        }

        private void ReadNews(object Sender, DownloadStringCompletedEventArgs e)
        {
            List<VideoObject> videoList = new List<VideoObject>();

            try
            {
                if (!e.Cancelled)
                {
                    XDocument xDoc = XDocument.Parse(e.Result);
                    XElement xml = XElement.Parse(e.Result);

                    List<VideoObject> lst = new List<VideoObject>();
                    var queue = from item in xDoc.Descendants("item")

                                select new VideoObject
                                {
                                    Heading = item.Element("title").Value,
                                    Url = item.Element("link").Value,
                                    Description = Helper.removeHtml(item.Element("description").Value),
                                    Posted = item.Element("pubDate").Value,
                                    videoList = (from img in item.Elements(item.GetNamespaceOfPrefix("media") + "group").Elements(item.GetNamespaceOfPrefix("media") + "content")
                                                 select img.Attribute("url").Value).ToList(),
                                    imageList = (from img in item.Elements(item.GetNamespaceOfPrefix("media") + "thumbnail")
                                                 select img.Attribute("url").Value).ToList(),
                                    Lenght = "",
                                    LenghtDetails = "",
                                    PostedDate = Helper.getPostedDate(item.Element("pubDate").Value),
                                    PostedDetails = Helper.getPostedDetails(item.Element("pubDate").Value),
                                    Tags = "",
                                    Type = "",
                                    Favorite = false
                                };

                    videoList = queue.ToList<VideoObject>();

                    List<VideoObject> channel9List = new List<VideoObject>();

                    if (IsolatedStorageSettings.ApplicationSettings.Contains("channel9List"))
                        channel9List = IsolatedStorageSettings.ApplicationSettings["channel9List"] as List<VideoObject>;
                    else
                        IsolatedStorageSettings.ApplicationSettings.Add("channel9List", channel9List);

                    foreach (VideoObject video in videoList)
                    {
                        if (Helper.isUnique(video.Url, channel9List))
                        {
                            channel9List.Add(video);
                        }
                    }

                    IsolatedStorageSettings.ApplicationSettings["channel9List"] = channel9List;
                }

                lboNews.ItemsSource = videoList;
            }
            catch (Exception ex)
            {
                //MessageBox.Show(ex.ToString());
            }

            lboNews.Visibility = Visibility.Visible;
            lblLoading.Visibility = Visibility.Collapsed;
        }

It’s worth to mention that I save every unique videoObjects to a VideoObject-list, named ‘channel9List’, in the isolated storage. This list is among others used by the search-component (because the Channel9-site don’t offer a search-webservice). More about that late.

The ‘News’ page xaml looks like this:

<Grid>
        <ListBox x:Name="lboNews" HorizontalAlignment="Left" Width="400" SelectionChanged="lboNews_SelectionChanged">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Border Background="White" Margin="0,0,0,30" Width="400" Opacity="0.5" />
                        <StackPanel Orientation="Vertical">
                            <StackPanel Orientation="Horizontal" Margin="0,0,20,0">
                                <Image Source="{ Binding getImage }" Width="180" Margin="0,0,10,10" VerticalAlignment="Top" HorizontalAlignment="Left" />
                                <TextBlock Text="{ Binding PublishedDate}" Margin="10,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Center" TextWrapping="Wrap" Style="{StaticResource PanoramaTextBlock}" />
                            </StackPanel>
                            <TextBlock Text="{ Binding Heading}" TextWrapping="Wrap" Margin="20,0,25,40" Style="{StaticResource PanoramaTextBlock}" />
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <TextBlock x:Name="lblLoading" Text="loading..." FontSize="30" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,-50,0,0" />
    </Grid>

When the user selects an item in the listbox control the method ‘lboNews_SelectionChanged’ will be called. You can see the method below and what it does is to save the selected videoObject to isolated storage. Afterwards I raise the ‘goToVideoInfo’ event to navigate to the Video-page. I have chosen this way of moving data between pages, insted of the “url-parameters way”, because by using the isolated storage I can “move” a lot of data between pages easier.

private void lboNews_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            VideoObject video = lboNews.SelectedItem as VideoObject;
            if (video != null)
            {
                if (IsolatedStorageSettings.ApplicationSettings.Contains("Video"))
                    IsolatedStorageSettings.ApplicationSettings["Video"] = video;
                else
                    IsolatedStorageSettings.ApplicationSettings.Add("Video", video);

                goToVideoInfo(video, new EventArgs());
                lboNews.SelectedIndex = -1;
            }
        }

The ‘VideoInfo’ page

This page is quite easy to understand, it just reads a VideoObject from Isolated storage and bindes the information to a couple off textblocks and a image control like this:

private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
        {
            if (IsolatedStorageSettings.ApplicationSettings.Contains("Video"))
            {
                video = IsolatedStorageSettings.ApplicationSettings["Video"] as VideoObject;
            }

            if (video != null && video.Image != "")
            {
                lblDate.Text = video.PostedDetails;
                lblHeading.Text = video.Heading;
                lblDescription.Text = video.Description;

                if (video.LenghtDetails != null && video.LenghtDetails != "")
                {
                    lblLenght.Visibility = Visibility.Visible;
                    lblLenght.Text = "Lenght: " + video.LenghtDetails;
                }

                Uri uri = new Uri(video.getImage, UriKind.Absolute);
                ImageSource imgSource = new BitmapImage(uri);
                img.Source = imgSource;

                showCorrectFavoriteIcon();
            }
        }

One of the features on this page is the ability to add a video as a ‘favorite’ by clicking on the star-icon. The code looks like this:

private void btnFavorite_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            List<VideoObject> channel9List = null;

            if (IsolatedStorageSettings.ApplicationSettings.Contains("channel9List"))
            {
                channel9List = IsolatedStorageSettings.ApplicationSettings["channel9List"] as List<VideoObject>;

                if (channel9List != null)
                {
                    if (video.Favorite)
                    {
                        channel9List.Remove(video);
                        video.Favorite = false;
                        channel9List.Add(video);
                    }
                    else
                    {
                        channel9List.Remove(video);
                        video.Favorite = true;
                        channel9List.Add(video);
                    }

                    showCorrectFavoriteIcon();
                }

                IsolatedStorageSettings.ApplicationSettings["channel9List"] = channel9List;
            }
        }

To stream the video you have to click on the play-icon in the top right corner, and the app will send you to the ‘Video’-page.

The ‘VideoInfo’ page xaml looks like this:

<Grid x:Name="LayoutRoot" Background="Black">
        <Grid x:Name="InfoView">
            <Image x:Name="imgBackground" Source="Images/bg.png" Height="800" Width="500" VerticalAlignment="Top" HorizontalAlignment="Left" />

            <Border Background="White" Margin="0,85,0,0" Width="450" Opacity="0.5" />
            <ScrollViewer Margin="15,85,15,0">
                <StackPanel Orientation="Vertical" >
                    <Image x:Name="img" Width="450" HorizontalAlignment="Center" VerticalAlignment="Top" />
                    <TextBlock x:Name="lblDate" Margin="20,0,20,0" TextWrapping="Wrap" Style="{StaticResource PanoramaTextBlock}" />
                    <TextBlock x:Name="lblHeading" FontWeight="Bold"  Margin="20,20,20,0" TextWrapping="Wrap" Style="{StaticResource PanoramaTextBlock}" />
                    <TextBlock x:Name="lblLenght" Visibility="Collapsed"  Margin="20,20,20,0" TextWrapping="Wrap" Style="{StaticResource PanoramaTextBlock}" />
                    <TextBlock x:Name="lblDescription"  Margin="20,20,20,0" TextWrapping="Wrap" Style="{StaticResource PanoramaTextBlock}" />
                </StackPanel>
            </ScrollViewer>
        </Grid>

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="15,15,0,0" >
            <Image Source="Images/imgStar.png" x:Name="btnFavorite" Width="48" Height="48" Margin="10,0,0,0" MouseLeftButtonDown="btnFavorite_MouseLeftButtonDown" />
        </StackPanel>

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="15,15,15,0" >
            <Image x:Name="btnPlay" Source="Images/imgPlay.png" Width="48" Height="48" MouseLeftButtonDown="btnPlay_MouseLeftButtonDown" />
        </StackPanel>

    </Grid>

The ‘Video’ page

This page is used to stream videos and support both portrait and landscape mode.

        private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
        {
            OrientationChanged += new EventHandler<OrientationChangedEventArgs>(Video_OrientationChanged);

            if (IsolatedStorageSettings.ApplicationSettings.Contains("Video"))
            {
                video = IsolatedStorageSettings.ApplicationSettings["Video"] as VideoObject;
            }

            if (video != null)
            {
                media.Loaded += new RoutedEventHandler(media_Loaded);
                media.CurrentStateChanged += new RoutedEventHandler(media_Loaded);
                media.Source = new Uri(video.getVideo);
                play();
                showCorrectFavoriteIcon();
            }
        }

        void Video_OrientationChanged(object sender, OrientationChangedEventArgs e)
        {
            if (Orientation == PageOrientation.Landscape || Orientation == PageOrientation.LandscapeLeft || Orientation == PageOrientation.LandscapeRight)
            {
                mediaControl.Width = 70;
                media.Margin = new Thickness(70, 0, 0, 0);

                btnFavorite.Visibility = Visibility.Collapsed;
                btnPlay.Visibility = Visibility.Collapsed;
            }
            else
            {
                mediaControl.Width = 0;
                media.Margin = new Thickness(0, 0, 0, 0);

                btnFavorite.Visibility = Visibility.Visible;
                btnPlay.Visibility = Visibility.Visible;
            }

            showCorrectFavoriteIcon();
        }

The ‘Video’ page xaml looks like this:

    <Grid x:Name="LayoutRoot" Background="Black">

        <MediaElement Visibility="Visible" x:Name="media" />

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="15,15,0,0" >
            <Image Source="Images/imgStar.png" x:Name="btnFavorite" Width="48" Height="48" Margin="10,0,0,0" MouseLeftButtonDown="btnFavorite_MouseLeftButtonDown" />
        </StackPanel>

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="15,15,15,0" >
            <Image x:Name="btnPlay" Source="Images/imgPlay.png" Width="48" Height="48" MouseLeftButtonDown="btnPlay_MouseLeftButtonDown" />
        </StackPanel>

        <StackPanel x:Name="mediaControl" Background="Black" Width="0" VerticalAlignment="Center" HorizontalAlignment="Left" Orientation="Vertical" Margin="0,0,0,0">
            <Image Source="Images/imgStar.png" x:Name="btnFavorite2" Width="48" Height="48" Margin="0,15,0,0" MouseLeftButtonDown="btnFavorite_MouseLeftButtonDown" />
            <Image x:Name="btnPlay2" Source="Images/imgPlay.png" Width="48" Height="48" Margin="0,15,0,0" MouseLeftButtonDown="btnPlay_MouseLeftButtonDown" />
        </StackPanel>

        <TextBlock x:Name="lblLoading" Text="loading..." FontSize="30" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,-50,0,0" />
    </Grid>

Download the source code:

Next post
In the next post, I will go through details on how I built the ‘Show’, ‘Tags’ and ‘Search’ features. I will also list some of the coming changes and new features.