We reserve the right to make changes to this policy.
Any changes to this policy will be updated.
I'll mostly be blogging about Windows Phone 7 development.
If you’ve been doing any Windows Phone development you may have run into Jeff Wilcox’s excellent blog: http://www.jeff.wilcox.name/blog/
He’s got on post in particular about the per issues with the built in ProgressBar: http://www.jeff.wilcox.name/2010/08/performanceprogressbar/
I’ll leave it as an exercise to follow this post as it’s quite simple. Roughly the steps are:
<ProgressBar x:Name="ProgressBar" VerticalAlignment="Top" IsIndeterminate="False" Visibility="Collapsed" Style="{StaticResource PerformanceProgressBar}"/>
Currently when our code launches MainPage.xaml is loaded the code behind will check if there is stored credentials. If none are found it will load the TwitterAuthPage.xaml to show the user they need to authorize their Twitter account. If you hit the back button you’ll be taken back to the MainPage.xaml file if you hit back button again the app will exit.
That seems ok right? According to app submission policy, launching the app followed by the back key must exit the app and it will fail validation (I hit this one already). There is a good solution however to this problem.
In App.xaml.cs add an event handler for navigating in the constructor (note I’ve taken the comments out of the code to make it easier to read.
public App()
{
UnhandledException += Application_UnhandledException;
if (System.Diagnostics.Debugger.IsAttached)
{
Application.Current.Host.Settings.EnableFrameRateCounter = true;
}
InitializeComponent();
InitializePhoneApplication();
RootFrame.Navigating += RootFrame_Navigating;
}
Under the “Properties” folder is the WMAppManifext.xml file we’re going to have it ask for a new xaml file when the app launches. However we won’t be creating a file for this. Change the “NavigationPage” attribute of the “DefaultTask node to:
<DefaultTask Name ="_default" NavigationPage="Views/HomePage.xaml"/>
Now we need to add the event handler. This handler will check to see if the “HomePage.xaml” file is ever called. If it’s called, we’ll then check if credentials have been stored, if they have load our MainPage.xaml otherwise load the TwitterAuthPage.xaml file:
private void RootFrame_Navigating(object sender, NavigatingCancelEventArgs e)
{
// Only care about HomePage
if (e.Uri.ToString().Contains("/HomePage.xaml") != true)
return;
e.Cancel = true;
// check if credentials are stored
var credentialsFound = false;
var twitterSettings = Helper.LoadSetting<TwitterAccess>(Constants.TwitterAccess);
if ((twitterSettings != null &&
!String.IsNullOrEmpty(twitterSettings.AccessToken) &&
!String.IsNullOrEmpty(twitterSettings.AccessTokenSecret)))
{
credentialsFound = true;
}
RootFrame.Dispatcher.BeginInvoke(delegate
{
RootFrame.Navigate(credentialsFound
? new Uri("/Views/MainPage.xaml", UriKind.Relative)
: new Uri("/Views/TwitterAuthPage.xaml", UriKind.Relative));
});
}
We now have to make a minor change to TwitterAuthPage.xaml.cs in the “RequestAccessTokenCompleted”. We use to just go back, but since this page can now be the first page, if we successfully get the token and there is no back history load the MainPage.xaml” file:
private void RequestAccessTokenCompleted(RestRequest request, RestResponse response, object userstate)
{
var twitteruser = new TwitterAccess
{
AccessToken = GetQueryParameter(response.Content, "oauth_token"),
AccessTokenSecret = GetQueryParameter(response.Content, "oauth_token_secret"),
UserId = GetQueryParameter(response.Content, "user_id"),
ScreenName = GetQueryParameter(response.Content, "screen_name")
};
if (String.IsNullOrEmpty(twitteruser.AccessToken) || String.IsNullOrEmpty(twitteruser.AccessTokenSecret))
{
Dispatcher.BeginInvoke(() => MessageBox.Show(response.Content));
return;
}
Helper.SaveSetting(Constants.TwitterAccess, twitteruser);
Dispatcher.BeginInvoke(() =>
{
if (NavigationService.CanGoBack)
{
NavigationService.GoBack();
}
else
{
NavigationService.Navigate(new Uri("/Views/MainPage.xaml", UriKind.Relative));
}
});
}
Now we can remove the check in MainPage.xaml.cs that we used to see if credentials were entered. The new MainPage_Loaded method looks like this:
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
That’s it, if you launch the app for the first time, the Twitter authorization page will show and hitting back will now exit the app. Launching after credentials have been stored will bring up the MainPage.xaml as it did before.
The source code is available at http://twitt.codeplex.com/
Sam
We’re getting pretty close a fully working twitter app. In this part we’re going to add a details page for tweets.
When a list item is selected from any of the lists (statuses, mentions, messages or favorites), the detail page will load the tweet.
The detail page will also contain the users name, the tweet date and the source of the tweet (source is usually the name of the application the user used to Tweet and will have a link to their web site).
We’ll also add an application bar with several options including add item as favorite, email item, reply, re-tweet, send direct message and open in Internet Explorer.
Let’s get started by creating our new “DetailPage”. Right click on “Views” –> “Add” –> “New Item…”. Select “Windows Phone Portrait Page” and give it the name “DetailPage.xaml” then hit the “Add” button:
Next, open the new “DetailPage.xaml” file and add the WebBrowser control. Here’s the complete xaml I’ve added:
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="Twitt" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,68">
<phone:WebBrowser x:Name="WebBrowser" Grid.Row="0" IsScriptEnabled="True"/>
</Grid>
</Grid>
I’ve set “IsScriptEnabled” to “true” to allow Javascript to run as users navigate links in the browser control.
Next we need a new helper class to store the data we’re going to pass to this page. Under “Common” add a new class called “DetailPageData.cs” which looks like this:
using System;
namespace Twitt.Common
{
public class DetailPageData
{
public String UserName { get; set; }
public String UserDisplayName { get; set; }
public String Text { get; set; }
public String CreatedDate { get; set; }
public String Source { get; set; }
public long id { get; set; }
}
}
We’ll also be using the same pattern I did in early lessons to store the data in IsolatedStorage to pass to the page. This is helpful to pass objects from page to page and also helps with Tombstoning since re-starting the app from any page can reload the same data from the saved file. There is probably a small perf hit, but I haven’t noticed it feeling slow in the app. But this could be a good candidate for future refactoring.
We’ve been storing all our file names in the “Constants.cs” file, so let’s add 2 new ones. One for the “DetailPageFileName” and another one we’ll use to pass data for replies and retweets called “TweetPageFileName”:
namespace Twitt.Common
{
public class Constants
{
public static string TwitterAccess = "TwitterAccess";
public static string StatusesFileName = "StatusesFile";
public static string MentionsFileName = "MentionsFile";
public static string DirectMessagesFileName = "MessagesFile";
public static string FavoritesFileName = "FavoritesFile";
public static string DetailPageFileName = "DetailPage";
public static string TweetPageFileName = "TweetPage";
}
}
Now we can work on our code behind file “DetailPage.xaml.cs”. Here’s the complete source code for this file:
using System;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Shell;
using Twitt.Common;
namespace Twitt.Views
{
public partial class DetailPage
{
private DetailPageData _detailItem;
public DetailPage()
{
InitializeComponent();
CreateApplicationBar();
Loaded += Post_Loaded;
}
#region ApplicationBar
private void CreateApplicationBar()
{
ApplicationBar = new ApplicationBar { IsMenuEnabled = true, IsVisible = true, Opacity = .9 };
var retweet = new ApplicationBarIconButton(new Uri("/Resources/Images/retweet.png", UriKind.Relative));
retweet.Text = "retweet";
retweet.Click += RetweetClick;
var reply = new ApplicationBarIconButton(new Uri("/Resources/Images/reply.png", UriKind.Relative));
reply.Text = "reply";
reply.Click += ReplyClick;
ApplicationBar.Buttons.Add(retweet);
ApplicationBar.Buttons.Add(reply);
}
private void RetweetClick(object sender, EventArgs e)
{
var tweetPage = new TweetPageData
{
Tweet = String.Format("RT @{0} {1}", _detailItem.UserName, _detailItem.Text)
};
// Save the detailpage object which the detailpage will load up
Helper.SaveSetting(Constants.TweetPageFileName, tweetPage);
NavigationService.Navigate(new Uri("/Views/TweetPage.xaml", UriKind.Relative));
}
private void ReplyClick(object sender, EventArgs e)
{
var tweetPage = new TweetPageData
{
Tweet = String.Format("@{0} ", _detailItem.UserName)
};
// Save the detailpage object which the detailpage will load up
Helper.SaveSetting(Constants.TweetPageFileName, tweetPage);
NavigationService.Navigate(new Uri("/Views/TweetPage.xaml", UriKind.Relative));
}
#endregion
protected override void OnNavigatedTo(NavigationEventArgs e)
{
_detailItem = Helper.LoadSetting<DetailPageData>(Constants.DetailPageFileName);
if (_detailItem == null)
{
MessageBox.Show("Error loading page data");
return;
}
PageTitle.Text = _detailItem.UserDisplayName;
}
private void Post_Loaded(object sender, RoutedEventArgs e)
{
var html = new StringBuilder();
html.Append(String.Format("{0}<br><br>", _detailItem.CreatedDate));
html.Append(MakeLinks(_detailItem.Text));
if (!String.IsNullOrEmpty(_detailItem.Source))
html.Append(String.Format("<br><br>Source: {0}", _detailItem.Source));
WebBrowser.NavigateToString(html.ToString());
}
private static string MakeLinks(string txt)
{
var regx = new Regex(@"http(s)?://([\w+?\.\w+])+([a-zA-Z0-9\~\!\@\#\$\%\^\&\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?", RegexOptions.IgnoreCase);
var mactches = regx.Matches(txt);
return mactches.Cast<Match>().Aggregate(txt, (current, match) => current.Replace(match.Value, "<a href='" + match.Value + "'>" + match.Value + "</a>"));
}
}
}
Let’s walk through a bit of that code: “CreateApplicationBar” will setup our 2 application bar icons. I created two icons for these and added them to the Images directory, make sure you set the properties to “content” or they won’t render. If you want to use the ones I used, grab the source code from the link at the bottom of this post.
In the "OnNavigatedTo” method, it first loads the data file for this page and stores it in a member variabled named “_detailItem”. The page title is then set to the users display name that sent that tweet:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
_detailItem = Helper.LoadSetting<DetailPageData>(Constants.DetailPageFileName);
if (_detailItem == null)
{
MessageBox.Show("Error loading page data");
return;
}
PageTitle.Text = _detailItem.UserDisplayName;
}
The constructor set a Post_Loaded event, this will be used to populate the Web Browser control with the tweet data. I’ll use a StringBuilder to create the HTML. I’ll first add the tweet date, then the tweet text followed by the source if there is one:
private void Post_Loaded(object sender, RoutedEventArgs e)
{
var html = new StringBuilder();
html.Append(String.Format("{0}<br><br>", _detailItem.CreatedDate));
html.Append(MakeLinks(_detailItem.Text));
if (!String.IsNullOrEmpty(_detailItem.Source))
html.Append(String.Format("<br><br>Source: {0}", _detailItem.Source));
WebBrowser.NavigateToString(html.ToString());
}
Notice in the above code I call a helper called “MakeLinks” this helper looks for any imbedded html links in the tweet text and creates an HTML anchor link using a RegEx pattern:
private static string MakeLinks(string txt)
{
var regx = new Regex(@"http(s)?://([\w+?\.\w+])+([a-zA-Z0-9\~\!\@\#\$\%\^\&\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?", RegexOptions.IgnoreCase);
var mactches = regx.Matches(txt);
return mactches.Cast<Match>().Aggregate(txt, (current, match) => current.Replace(match.Value, "<a href='" + match.Value + "'>" + match.Value + "</a>"));
}
The next step is to hook up the lists on our MainPage.xaml file to open this page if an item was selected. Open up MainPage.xaml and for all the list panorama items add SelectionChanged=”ListBoxSelectionChanged” event handler. We’ll have one handler handle them all:
<ListBox Margin="0,0,-12,32" ItemsSource="{Binding Items}" SelectionChanged="ListBoxSelectionChanged">
Now in the MainPage.xaml.cs file add the event handler. The event handler will first ensure an item was selected, then extract the data and create a new “DetailPageData” item and save it. Then it will navigate to our new “DetailPage.xaml” file:
private void ListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedIndex == -1)
return;
var selectedItem = (ItemViewModel)((ListBox)sender).SelectedItem;
if (selectedItem == null)
return;
var detailPage = new DetailPageData
{
UserDisplayName = selectedItem.DisplayUserName,
UserName = selectedItem.UserName,
CreatedDate = selectedItem.CreatedDate,
Text = selectedItem.TweetText,
Source = selectedItem.Source,
Id = selectedItem.Id
};
// Save the detailpage object which the detailpage will load up
Helper.SaveSetting(Constants.DetailPageFileName, detailPage);
NavigationService.Navigate(new Uri("/Views/DetailPage.xaml", UriKind.Relative));
}
Let’s go back to the “DetailPage.xaml.cs” file. I want to explain how I did the “Reply” and “Retweet”. Here’s a look at the code:
private void RetweetClick(object sender, EventArgs e)
{
var tweetPage = new TweetPageData
{
Tweet = String.Format("RT @{0} {1}", _detailItem.UserName, _detailItem.Text)
};
// Save the detailpage object which the detailpage will load up
Helper.SaveSetting(Constants.TweetPageFileName, tweetPage);
NavigationService.Navigate(new Uri("/Views/TweetPage.xaml", UriKind.Relative));
}
private void ReplyClick(object sender, EventArgs e)
{
var tweetPage = new TweetPageData
{
Tweet = String.Format("@{0} ", _detailItem.UserName)
};
// Save the detailpage object which the detailpage will load up
Helper.SaveSetting(Constants.TweetPageFileName, tweetPage);
NavigationService.Navigate(new Uri("/Views/TweetPage.xaml", UriKind.Relative));
}
Each method will create a new “TweetPageData” item and populate the “Tweet” variable with the data we want to show up. For re-tweets, we just add “RT” then the users name before the tweet and for replies, we just add the “@” plus the user name. We then navigate to our already existing TweetPage.xaml. We now have to add a bit of code in this file to read from this new file. I’ve changed the “OnNavigatedTo” method to look like this:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
_twitterSettings = Helper.LoadSetting<TwitterAccess>(Constants.TwitterAccess);
if (_twitterSettings == null) return;
((ApplicationBarIconButton)ApplicationBar.Buttons[0]).IsEnabled = !String.IsNullOrEmpty(_twitterSettings.AccessToken) && !String.IsNullOrEmpty(_twitterSettings.AccessTokenSecret);
var detailItem = Helper.LoadSetting<TweetPageData>(Constants.TweetPageFileName);
if (detailItem != null)
{
TweetTextBox.Text = detailItem.Tweet;
}
}
Let’s about all we need to add. Run the app and you should now be able to click on any tweet in your lists and see the following screen:
You can now click on any link and have it open up in the browser. Selecting “Retweet” will show:
Let’s add some more quick features. We’ll now add the ability to “favorite” and item, e-mail it, open the link in Internet Explorer and sending a direct messages to the user that tweeted the item. In DetailPage.xaml.cs file change the “CreateApplicationBar” method to:
private void CreateApplicationBar()
{
ApplicationBar = new ApplicationBar { IsMenuEnabled = true, IsVisible = true, Opacity = .9 };
var favorite = new ApplicationBarIconButton(new Uri("/Resources/Images/favorite.png", UriKind.Relative));
favorite.Text = "favorite";
favorite.Click += FavoriteClick;
ApplicationBar.Buttons.Add(favorite);
var sendEmail = new ApplicationBarIconButton(new Uri("/Resources/Images/mail.png", UriKind.Relative));
sendEmail.Text = "E-mail tweet";
sendEmail.Click += SendMailClick;
ApplicationBar.Buttons.Add(sendEmail);
var retweet = new ApplicationBarIconButton(new Uri("/Resources/Images/retweet.png", UriKind.Relative));
retweet.Text = "retweet";
retweet.Click += RetweetClick;
ApplicationBar.Buttons.Add(retweet);
var reply = new ApplicationBarIconButton(new Uri("/Resources/Images/reply.png", UriKind.Relative));
reply.Text = "reply";
reply.Click += ReplyClick;
ApplicationBar.Buttons.Add(reply);
var tweet = new ApplicationBarMenuItem("Send direct message");
tweet.Click += SendDirectMessageClick;
ApplicationBar.MenuItems.Add(tweet);
var browserItem = new ApplicationBarMenuItem("Open in Internet Explorer");
browserItem.Click += OpenInBrowserItemClick;
browserItem.IsEnabled = false;
ApplicationBar.MenuItems.Add(browserItem);
}
We’ll need the associated event methods for FavoriteClick, SendMailClick, SendDirectMessageClick and OpenInBrowserclick:
"SendMailClick” will set the Body to the tweet text and the subject to the user name:
private void SendMailClick(object sender, EventArgs e)
{
var task = new EmailComposeTask
{
Body = _detailItem.Text,
Subject = String.Format("Tweet by {0}", _detailItem.UserDisplayName)
};
task.Show();
}
“SendDirectMessageClick” will just append “d” then the user name to the front of the tweet:
private void SendDirectMessageClick(object sender, EventArgs e)
{
var tweetPage = new TweetPageData
{
Tweet = String.Format("d {0} ", _detailItem.UserName),
};
Helper.SaveSetting(Constants.TweetPageFileName, tweetPage);
NavigationService.Navigate(new Uri("/Views/TweetPage.xaml", UriKind.Relative));
}
“OpenInBrowserItemClick” will open the current URL in the Browser Control in Internet Explorer:
private void OpenInBrowserItemClick(object sender, EventArgs e)
{
var task = new WebBrowserTask { URL = WebBrowser.Source.ToString() };
task.Show();
}
I also want to add a progress bar to the xaml as well as turning it on when the favorite item is clicked as well as navigating.
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<ProgressBar x:Name="ProgressBar" VerticalAlignment="Top" IsIndeterminate="False" Visibility="Collapsed"/>
<TextBlock x:Name="ApplicationTitle" Text="Twitt" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
I also want to turn the progress bar on while navigating links so in DetailPage.xaml change the browser control to:
<phone:WebBrowser x:Name="WebBrowser" Grid.Row="0" IsScriptEnabled="True" Navigated="WebBrowserNavigated" Navigating="WebBrowserNavigating"/>
Now add the 2 new event handlers for “WebBrowserNavigated” and “WebBrowserNavigating” to the DetailPage.xaml.cs file. Notice once we’ve navigated I’m enabling the “open in internet explorer” link in the application bar. It’s the second item added so we reference by array element “1”:
private void WebBrowserNavigating(object sender, NavigatingEventArgs e)
{
ProgressBar.Visibility = Visibility.Visible;
ProgressBar.IsIndeterminate = true;
((ApplicationBarMenuItem)ApplicationBar.MenuItems[1]).IsEnabled = true;
}
private void WebBrowserNavigated(object sender, NavigationEventArgs e)
{
ProgressBar.Visibility = Visibility.Collapsed;
ProgressBar.IsIndeterminate = false;
}
We’ll add the Favorites event handler. We’ll turn the progress bar on at the top as well create a callback method to turn it off for success or failures.
private void FavoriteClick(object sender, EventArgs e)
{
ProgressBar.IsIndeterminate = true;
ProgressBar.Visibility = Visibility.Visible;
var twitter = new TwitterHelper();
twitter.FavoriteCompletedEvent += (sender2, e2) =>
{
ProgressBar.IsIndeterminate = false;
ProgressBar.Visibility = Visibility.Collapsed;
MessageBox.Show("Item added to favorites");
};
twitter.ErrorEvent += (sender2, e2) =>
{
ProgressBar.IsIndeterminate = false;
ProgressBar.Visibility = Visibility.Collapsed;
};
twitter.FavoriteItem(_detailItem.Id);
}
Next add the TwitterHelper.cs code that marks the favorite in Twitter including the new “FavoriteCompletedEvent” event handler:
public event EventHandler FavoriteCompletedEvent;
public void FavoriteItem(long id)
{
if (!_authorized)
{
if (ErrorEvent != null)
ErrorEvent(this, EventArgs.Empty);
return;
}
var path = String.Format("/favorites/create/{0}.xml", id);
var request = new RestRequest
{
Credentials = _credentials,
Path = path,
Method = WebMethod.Post
};
_client.BeginRequest(request, new RestCallback(FavoriteItemCompleted));
}
private void FavoriteItemCompleted(RestRequest request, RestResponse response, object userstate)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
if (response.StatusCode != HttpStatusCode.OK)
{
Helper.ShowMessage(String.Format("Error calling Twitter : {0}", response.StatusCode));
if (ErrorEvent != null)
ErrorEvent(this, EventArgs.Empty);
return;
}
if (FavoriteCompletedEvent != null)
FavoriteCompletedEvent(this, EventArgs.Empty);
});
}
We now need to the 2 new images we used for favorites and email application bar items. Look at the code link below to get the ones I used.
Running the app and selecting a tweet should now show the below shot. Notice the “open in internet explorer” is disabled.
Clicking on a link should show the progress bar while it’s loading and then when complete look something like this:
Hitting the “favorite” button will save an item to your Twitter favorites and look like this:
If you then go back to the main page and hit the “Refresh” button you should also see the new item in your favorites:
This app is getting pretty complete. One item missing might be to add a photo to a tweet, I won’t be covering this. However in my next post I want to cover 2 more items.
The first it a change to how the app starts up and checks if you’ve setup your Twitter account. If you haven’t it pops up the Twitter authorization page. The problem is you now have to hit the back button twice to exit the app. This will cause a failure if you submit the application to the Marketplace.
The second item is adding Jeff Wilcox’s more performant progress bar.
The code can be found at: http://twitt.codeplex.com
Sam
In today’s post I’m going to show how to extend the work we did in part 4 to add mentions (Tweet’s with your name in it), direct messages to you and favorites. I’ll also show how to add a ProgressBar to show users the progress while we’re downloading the tweets.
The first set of changes will make are to the MainPage.xaml file. We’ll be adding 3 new parorama items and also the progress bar.
Let’s start with the progress bar. I’m going to wrap the progress bar and panorama control in a StackPanel:
<Grid x:Name="LayoutRoot" Background="Transparent">
<StackPanel>
<ProgressBar x:Name="ProgressBar" VerticalAlignment="Top" IsIndeterminate="{Binding ProgressBarIsIndeterminate, Mode=OneWay}" Visibility="{Binding ProgressBarVisibility, Mode=OneWay }"/>
<!--Panorama control-->
<controls:Panorama Title="Twitt">
…
</controls:Panorama>
</StackPanel>
</Grid>
Here you can see the “StackPanel” node at the top of the xaml followed by the “ProgressBar”. I’m going to bind the visibility and IsIndeterminate properties to our ViewModel. In MainViewModel.cs let’s add these two new properties “ProgressBarIsIndeterminate” and “ProgressBarVisibility”:
private Visibility _progressBarVisibility = Visibility.Collapsed;
public Visibility ProgressBarVisibility
{
get
{
return _progressBarVisibility;
}
set
{
if (value != _progressBarVisibility)
{
_progressBarVisibility = value;
NotifyPropertyChanged("ProgressBarVisibility");
}
}
}
private bool _progressBarIsIndeterminate;
public bool ProgressBarIsIndeterminate
{
get
{
return _progressBarIsIndeterminate;
}
set
{
if (value != _progressBarIsIndeterminate)
{
_progressBarIsIndeterminate = value;
NotifyPropertyChanged("ProgressBarIsIndeterminate");
}
}
}
Now let’s add the 3 new parorama items to “MainPage.xaml”. You should notice they are almost identical to the “statuses” item we added in part 4. The only difference is the "Header” which it the title for the panorma item as well as the binding property.
<controls:PanoramaItem Header="mentions">
<ListBox Margin="0,0,-12,32" ItemsSource="{Binding MentionItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,0,17">
<Image Source="{Binding Image}" Margin="12,0,12,0" Width="50" Height="50" />
<StackPanel Width="361" >
<TextBlock Text="{Binding DisplayUserName}" FontSize="{StaticResource PhoneFontSizeExtraLarge}" Foreground="{Binding TitleColor}"/>
<TextBlock Text="{Binding CreatedDate}" Margin="0,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
<TextBlock Text="{Binding TweetText}" TextWrapping="Wrap" Margin="0,-6,12,0" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>
<controls:PanoramaItem Header="messages">
<ListBox Margin="0,0,-12,32" ItemsSource="{Binding DirectMessageItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,0,17">
<Image Source="{Binding Image}" Margin="12,0,12,0" Width="50" Height="50" />
<StackPanel Width="361" >
<TextBlock Text="{Binding DisplayUserName}" FontSize="{StaticResource PhoneFontSizeExtraLarge}" Foreground="{Binding TitleColor}"/>
<TextBlock Text="{Binding CreatedDate}" Margin="0,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
<TextBlock Text="{Binding TweetText}" TextWrapping="Wrap" Margin="0,-6,12,0" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>
<controls:PanoramaItem Header="favorites">
<ListBox Margin="0,0,-12,32" ItemsSource="{Binding FavoriteItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,0,17">
<Image Source="{Binding Image}" Margin="12,0,12,0" Width="50" Height="50" />
<StackPanel Width="361" >
<TextBlock Text="{Binding DisplayUserName}" FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
<TextBlock Text="{Binding CreatedDate}" Margin="0,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
<TextBlock Text="{Binding TweetText}" TextWrapping="Wrap" Margin="0,-6,12,0" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>
The above 3 items have 3 new binding properties “MentionItems”, “ DirectMessageitems” and “FavoriteItems”. These are just references to the same ItemViewModel data structure which we’ll add next to the MainViewModel.cs and instantiate them in the constructor. These items will each hold their corresponding tweet list data:
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<ItemViewModel> Items { get; private set; }
public ObservableCollection<ItemViewModel> MentionItems { get; private set; }
public ObservableCollection<ItemViewModel> DirectMessageItems { get; private set; }
public ObservableCollection<ItemViewModel> FavoriteItems { get; private set; }
public MainViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
MentionItems = new ObservableCollection<ItemViewModel>();
DirectMessageItems = new ObservableCollection<ItemViewModel>();
FavoriteItems = new ObservableCollection<ItemViewModel>();
}
To get our sample data to work within our xaml we have to add these new items to our “MainViewModelSampleData.xml” file. These are just copies of the statuses sample data what we already added in part 4 except they are using the new ViewModel items we defined above:
<ViewModels:MainViewModel.MentionItems>
<local:ItemViewModel DisplayUserName="User One" CreatedDate="09/9/2010 4:35pm" TweetText="Maecenas praesent accumsan bibendum dictumst eleifend facilisi faucibus habitant inceptos interdum lobortis nascetur" TitleColor="White"/>
<local:ItemViewModel DisplayUserName="User Two" CreatedDate="09/9/2010 3:35pm" TweetText="Pharetra placerat pulvinar sagittis senectus sociosqu suscipit torquent ultrices vehicula volutpat maecenas praesent" TitleColor="White"/>
<local:ItemViewModel DisplayUserName="User Three" CreatedDate="09/9/2010 2:35pm" TweetText="Accumsan bibendum dictumst eleifend facilisi faucibus habitant inceptos interdum lobortis nascetur pharetra placerat" TitleColor="Black"/>
<local:ItemViewModel DisplayUserName="User Four" CreatedDate="09/9/2010 1:35pm" TweetText="Pulvinar sagittis senectus sociosqu suscipit torquent ultrices vehicula volutpat maecenas praesent accumsan bibendum" TitleColor="Black"/>
</ViewModels:MainViewModel.MentionItems>
<ViewModels:MainViewModel.DirectMessageItems>
<local:ItemViewModel DisplayUserName="User One" CreatedDate="09/9/2010 4:35pm" TweetText="Maecenas praesent accumsan bibendum dictumst eleifend facilisi faucibus habitant inceptos interdum lobortis nascetur" TitleColor="White"/>
<local:ItemViewModel DisplayUserName="User Two" CreatedDate="09/9/2010 3:35pm" TweetText="Pharetra placerat pulvinar sagittis senectus sociosqu suscipit torquent ultrices vehicula volutpat maecenas praesent" TitleColor="White"/>
<local:ItemViewModel DisplayUserName="User Three" CreatedDate="09/9/2010 2:35pm" TweetText="Accumsan bibendum dictumst eleifend facilisi faucibus habitant inceptos interdum lobortis nascetur pharetra placerat" TitleColor="Black"/>
<local:ItemViewModel DisplayUserName="User Four" CreatedDate="09/9/2010 1:35pm" TweetText="Pulvinar sagittis senectus sociosqu suscipit torquent ultrices vehicula volutpat maecenas praesent accumsan bibendum" TitleColor="Black"/>
</ViewModels:MainViewModel.DirectMessageItems>
<ViewModels:MainViewModel.FavoriteItems>
<local:ItemViewModel DisplayUserName="User One" CreatedDate="09/9/2010 4:35pm" TweetText="Maecenas praesent accumsan bibendum dictumst eleifend facilisi faucibus habitant inceptos interdum lobortis nascetur" TitleColor="White"/>
<local:ItemViewModel DisplayUserName="User Two" CreatedDate="09/9/2010 3:35pm" TweetText="Pharetra placerat pulvinar sagittis senectus sociosqu suscipit torquent ultrices vehicula volutpat maecenas praesent" TitleColor="White"/>
<local:ItemViewModel DisplayUserName="User Three" CreatedDate="09/9/2010 2:35pm" TweetText="Accumsan bibendum dictumst eleifend facilisi faucibus habitant inceptos interdum lobortis nascetur pharetra placerat" TitleColor="Black"/>
<local:ItemViewModel DisplayUserName="User Four" CreatedDate="09/9/2010 1:35pm" TweetText="Pulvinar sagittis senectus sociosqu suscipit torquent ultrices vehicula volutpat maecenas praesent accumsan bibendum" TitleColor="Black"/>
</ViewModels:MainViewModel.FavoriteItems>
You should now be able to see the data in the xaml editor:
We are now ready to get the real data from Twitter. We will first modify the “MainViewModel.cs” file to build upon what we added in Part 4.
We are going to use our progress bar here, we need to first add some variables to track when the 4 lists (statuses, mentions, DMs & favorites) are complete. When they are all complete we want to turn off the progress bar.
At the top of the file add these variables which will be used to sync each of the 4 calls to Twitter.
private static readonly Object ThisLock = new Object();
private int _syncCount;
private const int SyncItemCount = 4;
Now change the “LoadData” method to reset the “_syncCount”, turn the progress bar on and call the LoadList method for each.
private void LoadData(bool refresh)
{
_syncCount = SyncItemCount;
ProgressBarIsIndeterminate = true;
ProgressBarVisibility = Visibility.Visible;
LoadList(TwitterListType.Statuses, refresh);
LoadList(TwitterListType.Mentions, refresh);
LoadList(TwitterListType.DirectMessages, refresh);
LoadList(TwitterListType.Favorites, refresh);
}
private void LoadList(TwitterListType listType, bool refresh)
{
LoadList(listType, refresh, String.Empty);
}
Now in the “LoadList” method add the new lists to our switch statement.
private void LoadList(TwitterListType listType, bool refresh, string searchTerm)
{
string fileName = null;
ObservableCollection<ItemViewModel> parentList = null;
switch (listType)
{
case TwitterListType.Statuses:
fileName = Constants.StatusesFileName;
parentList = Items;
break;
case TwitterListType.Mentions:
fileName = Constants.MentionsFileName;
parentList = MentionItems;
break;
case TwitterListType.DirectMessages:
fileName = Constants.DirectMessagesFileName;
parentList = DirectMessageItems;
break;
case TwitterListType.Favorites:
fileName = Constants.FavoritesFileName;
parentList = FavoriteItems;
break;
}
if (String.IsNullOrEmpty(fileName))
return;
// If exists, bind it first then go update unless we are refreshing
if (!refresh)
{
var itemList = Helper.LoadSetting<List<ItemViewModel>>(fileName);
if (itemList != null)
{
BindList(parentList, itemList);
}
}
var twitterHelper = new TwitterHelper();
twitterHelper.LoadList(listType, (parentList != null && parentList.Count > 0) ? parentList[0].Id : 0, searchTerm);
twitterHelper.LoadedCompleteEvent += (sender, e) =>
{
CheckCount();
var list = Helper.LoadSetting<List<ItemViewModel>>(fileName);
if (list == null)
{
Helper.ShowMessage("Error Loading Data from Twitter.");
return;
}
Deployment.Current.Dispatcher.BeginInvoke(() => BindList(parentList, list));
};
I’ve also added a helper method “CheckCount”, to the callback. This method will sync all 4 callbacks from Twitter after they are called and turn off the progress bar when they are done. I’ve used a simple thread lock here since the callbacks will come back on different threads.
private void CheckCount()
{
lock (ThisLock)
{
_syncCount--;
if (_syncCount == 0)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
ProgressBarIsIndeterminate = false;
ProgressBarVisibility = Visibility.Collapsed;
});
}
}
}
Notice above that list item in the “LoadList” method has a constant filename associated with it. So we need to add these new constants to the Constants.cs file:
public class Constants
{
public static string TwitterAccess = "TwitterAccess";
public static string StatusesFileName = "StatusesFile";
public static string MentionsFileName = "MentionsFile";
public static string DirectMessagesFileName = "MessagesFile";
public static string FavoritesFileName = "FavoritesFile";
}
The last part left is the calls to the Twitter API. These will follow the same pattern I used in part 4 for the “LoadStatuses” method. Here’s the new additions in TwitterHelper.cs. The first addition is to the “LoadList” method to load the appropriate method for each list.
public void LoadList(TwitterListType listType, long sinceId, string searchTerm)
{
switch (listType)
{
case TwitterListType.Statuses:
LoadStatuses(sinceId);
break;
case TwitterListType.Mentions:
LoadMentions(sinceId);
break;
case TwitterListType.DirectMessages:
LoadDirectMessages(sinceId);
break;
case TwitterListType.Favorites:
LoadFavorites();
break;
default:
return;
}
}
Next we’ll add each list method and their callback functions. Here is the code for the first one to get “Mentions”:
public void LoadMentions(long sinceId)
{
if (!_authorized)
{
if (LoadedCompleteEvent != null)
LoadedCompleteEvent(this, EventArgs.Empty);
return;
}
var request = new RestRequest
{
Credentials = _credentials,
Path = "/statuses/mentions.xml",
};
request.AddParameter("count", MaxCount);
if (sinceId != 0)
request.AddParameter("since_id", sinceId.ToString());
request.AddParameter("include_rts", "1");
_client.BeginRequest(request, new RestCallback(TwitterGetMentionsCompleted));
}
private void TwitterGetMentionsCompleted(RestRequest request, RestResponse response, object userstate)
{
if (response.StatusCode != HttpStatusCode.OK)
{
Helper.ShowMessage(String.Format("Twitter Error: {0}", response.StatusCode));
return;
}
var xmlElement = XElement.Parse(response.Content);
var mentionsList = (from item in xmlElement.Elements("status")
select new ItemViewModel
{
UserName = GetChildElementValue(item, "user", "screen_name"),
DisplayUserName = GetChildElementValue(item, "user", "name"),
TweetText = (string)item.Element("text"),
CreatedDate = GetCreatedDate((string)item.Element("created_at")),
Image = GetChildElementValue(item, "user", "profile_image_url"),
Id = (long)item.Element("id"),
NewTweet = true,
Source = (string)item.Element("source"),
}).ToList();
// Load cached file and add them but only up to 200 old items
var oldItems = Helper.LoadSetting<List<ItemViewModel>>(Constants.MentionsFileName);
if (oldItems != null)
{
var maxCount = (oldItems.Count < 200) ? oldItems.Count : 200;
for (var i = 0; i < maxCount; i++)
{
oldItems[i].NewTweet = false;
mentionsList.Add(oldItems[i]);
}
}
Helper.SaveSetting(Constants.MentionsFileName, mentionsList);
if (LoadedCompleteEvent != null)
LoadedCompleteEvent(this, EventArgs.Empty);
}
Here’s similar code to get direct messages:
public void LoadDirectMessages(long sinceId)
{
if (!_authorized)
{
if (LoadedCompleteEvent != null)
LoadedCompleteEvent(this, EventArgs.Empty);
return;
}
var request = new RestRequest
{
Credentials = _credentials,
Path = "/direct_messages.xml",
};
request.AddParameter("count", MaxCount);
if (sinceId != 0)
request.AddParameter("since_id", sinceId.ToString());
_client.BeginRequest(request, new RestCallback(TwitterGetDirectMessagesCompleted));
}
private void TwitterGetDirectMessagesCompleted(RestRequest request, RestResponse response, object userstate)
{
if (response.StatusCode != HttpStatusCode.OK)
{
Helper.ShowMessage(String.Format("Twitter Error: {0}", response.StatusCode));
return;
}
var xmlElement = XElement.Parse(response.Content);
var mentionsList = (from item in xmlElement.Elements("direct_message")
select new ItemViewModel
{
UserName = GetChildElementValue(item, "sender", "screen_name"),
DisplayUserName = GetChildElementValue(item, "sender", "name"),
TweetText = (string)item.Element("text"),
CreatedDate = GetCreatedDate((string)item.Element("created_at")),
Image = GetChildElementValue(item, "sender", "profile_image_url"),
Id = (long)item.Element("id"),
NewTweet = true,
Source = (string)item.Element("source"),
}).ToList();
// Load cached file and add them but only up to 200 old items
var oldItems = Helper.LoadSetting<List<ItemViewModel>>(Constants.DirectMessagesFileName);
if (oldItems != null)
{
var maxCount = (oldItems.Count < 200) ? oldItems.Count : 200;
for (var i = 0; i < maxCount; i++)
{
oldItems[i].NewTweet = false;
mentionsList.Add(oldItems[i]);
}
}
Helper.SaveSetting(Constants.DirectMessagesFileName, mentionsList);
if (LoadedCompleteEvent != null)
LoadedCompleteEvent(this, EventArgs.Empty);
}
Here’s the last code to get favorites:
public void LoadFavorites()
{
if (!_authorized)
{
if (LoadedCompleteEvent != null)
LoadedCompleteEvent(this, EventArgs.Empty);
return;
}
var request = new RestRequest
{
Credentials = _credentials,
Path = "/favorites.xml",
};
_client.BeginRequest(request, new RestCallback(TwitterGetFavoritesCompleted));
}
private void TwitterGetFavoritesCompleted(RestRequest request, RestResponse response, object userstate)
{
if (response.StatusCode != HttpStatusCode.OK)
{
Helper.ShowMessage(String.Format("Twitter Error: {0}", response.StatusCode));
return;
}
var xmlElement = XElement.Parse(response.Content);
var list = (from item in xmlElement.Elements("status")
select new ItemViewModel
{
UserName = GetChildElementValue(item, "user", "screen_name"),
DisplayUserName = GetChildElementValue(item, "user", "name"),
TweetText = (string)item.Element("text"),
CreatedDate = GetCreatedDate((string)item.Element("created_at")),
Image = GetChildElementValue(item, "user", "profile_image_url"),
Id = (long)item.Element("id"),
NewTweet = true,
Source = (string)item.Element("source"),
}).ToList();
if (list.Count > 0)
{
Helper.SaveSetting(Constants.FavoritesFileName, list);
}
if (LoadedCompleteEvent != null)
LoadedCompleteEvent(this, EventArgs.Empty);
}
That’s it, rebuild the app now and run it. Once you’ve entered in your Twitter Account information (if you haven’t yet), you should see the progress bar moving at the top, while new data is fetched. After it’s been run at least once the next time you launch it, the cached data will be displayed for all lists then an update will happen to get new tweets.
Here’s a complete list of what the UI should now look like:
In the next post I’ll show how to handle selecting a tweet to display on it’s on page. I’ll also translate any links in the tweets to real links and place the tweet in a web browser control. This page will also allow you to reply, re-tweet or direct message the user, using the tweet page we already created in a previous post.
Download part 5 source code from: http://twitt.codeplex.com/
Sam
On October 3rd, 2010 “Feed Reader” was released as one of the first Windows Phone 7 apps in the marketplace and the first RSS Reader. This post is going to cover the details of the app and hopefully serve as an FAQ to help new users with the app.
Feed Reader is ‘mostly’ a Google Reader client but it’s also a full Twitter application.
When you start the app for the first time it will ask you for your Google Reader account information:
Good question! A true RSS reader application needs to continually request all your RSS feeds to see what’s new and cache them. This is necessary because most RSS feeds will only serve up the most recent 10-20 news items. This poses a problem with a Windows Phone app, since once you exit the application it doesn’t run in the background. Therefore, it can’t constantly check for new feed items from your favorite feeds.
Along come services like Google Reader. Google Reader is one of the most popular news reader web apps. If you check out the Android or Apple Marketplaces, you’ll find many versions of Google Reader apps.
Some people, including me, rely on their phones for their daily news. In fact, it’s one of the first things I do when I wake up each morning. It is my major source of news. Almost all major news/sports/tech/blog sites offer RSS feeds and it’s super simple to add them to this app.
Either go to http://www.google.com/reader/ and create one, or hit the link in the app to take you there on the phone.
Once you’re created an account or if you already had one, enter in your email and password and hit the “save” checkmark at the bottom.
You should now see this screen if you’ve never subscribed to any feeds:
There are two ways: use http://www.google.com/reader or use this application directly. Notice at the bottom of the main screen there is a plus “+” button that says “add feed”.
You should now see this screen:
Select the textbox where it says “Enter search term here!” and enter in a search term. If you know the feed you’re looking for (perhaps a tech site like “Engadget” or news site like “ABC”) type it in. If you are just browsing, enter a more generic term.
Now hit the “Enter” key or the “search” button at the bottom to get the results:
Select the feed you want (or refine your search query):
If you’re happy with this name, press the “save” check button or hit the name box and change the name of the feed. Keeping these as short names makes it easier to read in the app.
Once you save it, you’ll be taken back to the list results, and will now see a check beside the newly subscribed feed. In addition, when doing searches you’ll always see a check beside feeds you’ve already subscribed to:
Add a few more feeds and when you’re done hit the back button: Notice the status at the top shows as these feeds automatically sync. To sync again, just hit the refresh button.
You bet, that’s easy! Just select one of the feeds and hit the “…” in the application bar at the bottom. Select “unsubscribe feed”:
Yes, see the above screen shot “rename feed”, selecting that will bring the following screen:
Yes. Notice the screen shot above has a “move feed” item. Select that and you get a list of folders you’ve already created or you can add a new one. This screen shot shows what it looks like with no folders:
I’ll created a folder called “Tech”:
Once you’ve created some folders, moving feeds gets much easier since you can just select an existing one if you like:
Once you’ve added some folders, your main screen will look something like this:
The numbers on the right in blue (this will change to match your phone accent color) indicates the number of unread items.
You can change this to “small”, “medium” or “large” in the settings. The settings can be accessed from the main screens. Here’s a complete look at all the settings:
Here’s how all three sizes compare:
Google Reader supports all of thee, “Favorites are equivalent to "Starred Items” in Google which are your favoritted items. As you are reading posts you can hit the “Star” to favorite an item.
After you “Favorite” an item you’ll see a little note at the top confirming it was completed successfully:
Now if you go back to the main menu and select “Favorites”, it will sync and you’ll see the items you have added:
Selecting an item will now change the “Favorite” button to “remove” which will remove the item from your favorites:
“Shared Items” works very similarly to “Favorites”. Shared items allow other users to subscribe to a feed of your “Shared” items in Google Reader. To share an item, select “share item with Google Reader” item from the application menu at the bottom:
Yes. In the settings section there are options to hide all or some of these. If you hide them all, your home screen will look something like this:
There is an option in settings to “Hide unread count”. If you turn this on, you’ll get a small graphic that new items are there but no count:
Yes, there’s an option for that.
Yes, there’s a setting for that:
Yes there’s an option to hide those:
Yes, there’s an option in settings called “Hide item details”. The following shows a feed list with details on (which is the default):
Here details are turned off to allow you to see more list items on the screen:
I like to read each item in feed list, can I avoid the feed list and go directly to the item view?
Yes, select “Hide feed list” in settings. One note is the feed last has some options like renaming the feed, moving the feed and viewing the live feed, if this view is hidden you’ll need to re-enable it to get those options.
In this section I’ll show some of the more advanced features and settings.
The first is one of my favorites which is the “markAllRead” Button, which is available on both a folder list or a single feed. This will automatically mark the whole feed as read and take you back to the previous screen, so you can quickly move to your next feed list:
Some feeds have little or no “details” with each feed item, here’s an example:
Selecting an item in this case will only show the title:
Notice in the feed list view there is a menu item called “show live details”. This is handy for some feeds that provide little to no data in the description of the item. This can be turned on per feed. Notice this feed doesn’t have any “details” under each item in the list:
** Hint: Clicking on the title will load the html version of the feed:
Clicking on the title here will bring up the live web page for the item:
However, if you turn on the “show live details page” instead of the html view above it will take you directly to the live web page.
You can easily turn this off by selecting the “hide live details page” once it has been turned on (similar to how you turned it on).
There may be times when you’ve read all the news items and on the phone the app has only cached the latest couple of items. You can always go view the real live feed instead of the cached one Google Reader sent us. Notice the “view live feed” item below:
Click on it and you’ll get this page, which looks similar to our cached version, only it’s coming from the live feed. The number of items will vary by feed as to how many they return:
In this section I’m will show some specifics about the item detail page (once you’ve selected an individual item from a feed):
This view shows the title at the top, followed by an image if there is one for the article then the cached details.
Notice the right side of the screen shows a preview and hint that there are more articles to the right. Flicking right/left will go to the next or previous item. Selecting the title will bring up the full html version of the cached page, which will possibly show more images and data than the above page.
Hitting the title or links now will bring you to the live html pages.
I briefly mentioned that clicking on the title will bring up the live page, or clicking links will go to those links while staying in the app:
There are several options available on this page:
I’ve already mentioned earlier in the post the “favorite” button to add to Google Favorites, and the “Share item with google reader” which shares it. You can also email the link, post it to Twitter, send it to “Read it Later” or “Instapaper” if you have accounts setup with those social sites.
The first time you select “send to twitter” you’ll get a twitter web page like this:
Notice the “Sign In to Twitter” button is there and also the “save” checkbox is disabled. You need to setup your Twitter account first. After selecting the “sign in” button you’ll get the screen below. Scrolling down you’ll be asked to enter your Twitter account and password to grant access to “Feed Reader”.
Once you’ve successfully entered it in, you’ll get this screen while your account access is being granted (note don’t hit the back key or your account may not be fully configured and you may have to start again).
After a few more seconds you should be re-directed back to your Tweet page. Notice the sign in button is gone and the save checkmark is enabled. Hitting the save button will tweet the message and take you back to the previous screen if there are no errors. Notice above the message you see how many characters you have remaining. The URLs are also shrunk with the bit.ly URL shrinking service:
You can also click on the message and change it if you like before you tweet.
Finally on the details page you can also select “mark item unread” if you’d like to keep an item unread.
When Feed Reader starts, it syncs with Google Reader. If you do not have a connection an error message will pop up. This is OK, you can still continue to read the news you’ve previously sunk up. If you’re about to take plane ride or go somewhere else where you will not have connectivity, just ensure you launch the app while you still have WI-FI or cellular coverage to sync all your news. You’re now ready to take your news with you.
When reading feed items, if you are not online you will not see images in the details as they are only loaded when online. Features such as adding as favorite, sharing or tweeting will not work when offline.
The app will, however, keep track of read items and marking lists as read. The next time you have connectivity it will mark those items as read and keep them in sync.
Full Twitter integration has been added to this application. Twitter lists are very similar to RSS feed lists.
If you have not set up your Twitter account yet within the app (see above section on sending tweets for feed items), you will see this screen with selecting “Twitter” from the main menu:
Once you’ve signed or if you already had, you’ll see this screen. You’ll need to hit the “refresh” button to go get your new friend statuses, mentions, direct messages and favorites:
Once you’ve hit refresh, you should see a screen similar to the below image with your user image at the top and counts for new items:
Selecting “Friend Statuses” will show:
Selecting a Tweet will show the Tweet details with embedded links which will load right in the app. The user will also now be at the top:
There are several options on this page, including Re-Tweeting, Replying, Emailing the tweet to someone, sending a direct message to this user, adding the item to Twitter Favorites or opening the link (if you’ve selected one in Internet Explorer):
Selecting the User will give you the below screen, where you can now check out their statuses, friends, followers and favorites (note each of these will only list the recent 100 items, but I’ll be adding a “more…” link to this in a future version to page through all their items. You can also “Follow” or “Unfollow” a user here.
If you click on your own user on the first Twitter screen, you’ll also get a similar page, where you can see your friends, followers, favorites and your own tweets.
Selecting “Trending” from the start Twitter page will give you the latest 10 trending items:
Selecting an item will bring up the search screen with a search for the trending topic (or you can get here hitting the search button from the main Twitter page):
Sending a new Tweet is easy from the main Twitter page:
That’s all for this post! I hope you enjoy the app. Please post any questions, comments, or feature requests.
Sam