Merge branch 'mvp_refactor' into develop

This commit is contained in:
mgroves 2011-03-30 21:41:10 -04:00
commit e0df956f7c
117 changed files with 2483 additions and 746 deletions

View file

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Android.Content;
using Android.Runtime;
using MonoStockPortfolio.Entities;
namespace MonoStockPortfolio.Core.Config

View file

@ -21,6 +21,8 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -29,6 +31,8 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
@ -58,7 +62,7 @@
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MonoDroid.FileHelpers\MonoDroid.LumenWorks.Framework.IO.Csv.csproj">
<ProjectReference Include="..\MonoDroid.LumenWorks.Framework.IO.Csv\MonoDroid.LumenWorks.Framework.IO.Csv.csproj">
<Project>{1AAA2739-D853-41B0-866B-B55B373616E1}</Project>
<Name>MonoDroid.LumenWorks.Framework.IO.Csv</Name>
</ProjectReference>

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Android.Content;
using Android.Database.Sqlite;
using Android.Runtime;
using Android.Util;
using MonoStockPortfolio.Entities;

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Android.Runtime;
using Android.Util;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Core.StockData;

View file

@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Linq;
using Machine.Specifications;
using MonoStockPortfolio.Framework;
namespace MonoStockPortfolio.Tests.Framework
{
public class When_validating_forms_with_validation_errors
{
static FormValidator _validator;
static IEnumerable<string> _errors;
Establish context = () =>
{
_validator = new FormValidator();
};
Because of = () =>
{
_validator.AddRequired(() => "", "This is required");
_validator.AddValidDecimal(() => "not a decimal", "Decimal required");
_validator.AddValidPositiveDecimal(() => "-18.9", "Positive decimal required");
_validator.AddValidation(() => "arbitrary error!");
_errors = _validator.Apply();
};
It should_return_1_error_message = () =>
_errors.Count().ShouldEqual(4);
It should_have_a_required_message = () =>
_errors.Any(e => e == "This is required").ShouldBeTrue();
It should_have_a_valid_decimal_message = () =>
_errors.Any(e => e == "Decimal required").ShouldBeTrue();
It should_have_a_valid_positive_decimal_message = () =>
_errors.Any(e => e == "Positive decimal required").ShouldBeTrue();
It should_have_an_arbitrary_message = () =>
_errors.Any(e => e == "arbitrary error!").ShouldBeTrue();
}
}

View file

@ -21,6 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<NoWarn>169</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -31,12 +32,19 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Machine.Specifications">
<HintPath>..\packages\Machine.Specifications.0.4.8.0\lib\Machine.Specifications.dll</HintPath>
</Reference>
<Reference Include="Mono.Android">
<Private>True</Private>
</Reference>
<Reference Include="mscorlib">
<Private>True</Private>
</Reference>
<Reference Include="PostSharp.SL, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b13fd38b8f9c99d7, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\libs\PostSharp.SL.dll</HintPath>
</Reference>
<Reference Include="System">
<Private>True</Private>
</Reference>
@ -46,16 +54,19 @@
<Reference Include="System.Xml.Linq">
<Private>True</Private>
</Reference>
<Reference Include="System.Xml">
<Private>True</Private>
</Reference>
<Reference Include="xunit">
<HintPath>..\libs\xunit.dll</HintPath>
<Reference Include="Telerik.JustMock.Silverlight">
<HintPath>..\libs\Telerik.JustMock.Silverlight.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Framework\When_validating_forms_with_validation_errors.cs" />
<Compile Include="Presenters\Given_an_initialized_Config_Presenter.cs" />
<Compile Include="Presenters\EditPortfolioTests.cs" />
<Compile Include="Presenters\EditPositionTests.cs" />
<Compile Include="Presenters\MainPresenterTests.cs" />
<Compile Include="Presenters\PortfolioPresenterTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="YahooStockDataProviderTests.cs" />
<Compile Include="Services\YahooStockDataServiceTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MonoStockPortfolio.Core\MonoStockPortfolio.Core.csproj">
@ -72,6 +83,7 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="PostSharp.Custom.targets" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" />

View file

@ -0,0 +1,113 @@
using System.Collections.Generic;
using System.Linq;
using Machine.Specifications;
using MonoStockPortfolio.Activites.EditPortfolioScreen;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Entities;
using Telerik.JustMock;
using Telerik.JustMock.Helpers;
namespace MonoStockPortfolio.Tests.Presenters
{
public class EditPortfolioTests
{
protected static EditPortfolioPresenter _presenter;
protected static IPortfolioRepository _mockPortfolioRepository;
protected static IEditPortfolioView _mockEditPortfolioView;
Establish context = () =>
{
_mockPortfolioRepository = Mock.Create<IPortfolioRepository>();
Mock.Arrange(() => _mockPortfolioRepository.GetPortfolioById(999)).Returns(
new Portfolio(999) {Name = "Testing Portfolio!"});
_mockEditPortfolioView = Mock.Create<IEditPortfolioView>();
_presenter = new EditPortfolioPresenter(_mockPortfolioRepository);
};
}
public class When_initializing_the_edit_portfolio_presenter_with_no_id : EditPortfolioTests
{
Because of = () =>
{
_presenter.Initialize(_mockEditPortfolioView, null);
};
It should_set_the_title_to_Add_New_Portfolio = () =>
Mock.Assert(() => _mockEditPortfolioView.SetTitle("Add New Portfolio"), Occurs.Exactly(1));
It shouldnt_prepopulate_the_form_with_anything = () =>
Mock.Assert(() => _mockEditPortfolioView.PopulateForm(Arg.IsAny<Portfolio>()), Occurs.Never());
}
public class When_initializing_the_edit_portfolio_presenter_with_an_id : EditPortfolioTests
{
Because of = () =>
{
_presenter.Initialize(_mockEditPortfolioView, 999);
};
It should_set_the_title_to_Edit_Portfolio = () =>
Mock.Assert(() => _mockEditPortfolioView.SetTitle("Edit Portfolio"), Occurs.Exactly(1));
It should_prepopulate_the_form_with_a_portfolio_name = () =>
Mock.Assert(() => _mockEditPortfolioView.PopulateForm(Arg.Matches<Portfolio>(x => x.Name == "Testing Portfolio!")), Occurs.Exactly(1));
}
public class When_the_user_wants_to_save_a_valid_portfolio : EditPortfolioTests
{
Establish context = () =>
{
_presenter.Initialize(_mockEditPortfolioView, null);
};
Because of = () =>
{
_presenter.SavePortfolio(new Portfolio(999) {Name = "Whatever Portfolio"});
};
It should_use_the_repository_to_save_the_portfolio = () =>
Mock.Assert(() => _mockPortfolioRepository.SavePortfolio(Arg.Matches<Portfolio>(x => x.ID == 999 && x.Name == "Whatever Portfolio")), Occurs.Exactly(1));
It should_tell_the_view_to_show_a_nice_saved_message = () =>
Mock.Assert(() => _mockEditPortfolioView.ShowSaveSuccessMessage("You saved: Whatever Portfolio"), Occurs.Exactly(1));
It should_tell_the_view_to_go_back_to_the_main_activity = () =>
Mock.Assert(() => _mockEditPortfolioView.GoBackToMainActivity(), Occurs.Exactly(1));
}
public class When_the_user_tries_to_save_a_new_portfolio_with_a_blank_name : EditPortfolioTests
{
Establish context = () =>
{
_presenter.Initialize(_mockEditPortfolioView);
};
Because of = () =>
{
_presenter.SavePortfolio(new Portfolio {Name = ""});
};
It should_return_1_validation_error = () =>
Mock.Assert(() => _mockEditPortfolioView.ShowValidationErrors(Arg.Matches<IEnumerable<string>>(x => x.Count() == 1)), Occurs.Exactly(1));
It should_return_a_nice_required_validation_error_message = () =>
Mock.Assert(() => _mockEditPortfolioView.ShowValidationErrors(Arg.Matches<IEnumerable<string>>(x => x.Single() == "Please enter a portfolio name")), Occurs.Exactly(1));
}
public class When_the_user_tries_to_save_a_portfolio_with_a_duplicated_name : EditPortfolioTests
{
Establish context = () =>
{
_presenter.Initialize(_mockEditPortfolioView);
};
Because of = () =>
{
Mock.Arrange(() => _mockPortfolioRepository.GetPortfolioByName(Arg.AnyString)).Returns(
new Portfolio(998) {Name = "Some Name"});
_presenter.SavePortfolio(new Portfolio {Name = "Some Name"});
};
It should_return_1_validation_error = () =>
Mock.Assert(() => _mockEditPortfolioView.ShowValidationErrors(Arg.Matches<IEnumerable<string>>(x => x.Count() == 1)), Occurs.Exactly(1));
It should_return_a_nice_duplication_error_message = () =>
Mock.Assert(() => _mockEditPortfolioView.ShowValidationErrors(Arg.Matches<IEnumerable<string>>(x => x.Single() == "Portfolio name is already taken")), Occurs.Exactly(1));
}
}

View file

@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Machine.Specifications;
using MonoStockPortfolio.Activites.EditPositionScreen;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Core.StockData;
using MonoStockPortfolio.Entities;
using Telerik.JustMock;
using Telerik.JustMock.Helpers;
namespace MonoStockPortfolio.Tests.Presenters
{
public class EditPositionTests
{
protected static EditPositionPresenter _presenter;
protected static IPortfolioRepository _mockPortfolioRepository;
protected static IStockDataProvider _mockStockService;
protected static IEditPositionView _mockView;
Establish context = () =>
{
_mockPortfolioRepository = Mock.Create<IPortfolioRepository>();
_mockStockService = Mock.Create<IStockDataProvider>();
_mockView = Mock.Create<IEditPositionView>();
_presenter = new EditPositionPresenter(_mockPortfolioRepository, _mockStockService);
};
}
public class When_initializing_the_edit_position_presenter_with_no_id : EditPositionTests
{
Because of = () =>
{
_presenter.Initialize(_mockView, 1);
};
It should_set_the_title_to_Add_Position = () =>
Mock.Assert(() => _mockView.SetTitle("Add Position"), Occurs.Exactly(1));
It shouldnt_prepopulate_the_form_with_anything = () =>
Mock.Assert(() => _mockView.PopulateForm(Arg.IsAny<Position>()),Occurs.Never());
}
public class When_initializing_the_edit_position_presenter_with_an_id : EditPositionTests
{
Establish context = () =>
{
var fakePosition = new Position(999) { ContainingPortfolioID = 1, PricePerShare = 5.99M, Shares = 50M, Ticker = "FAKE" };
Mock.Arrange(() => _mockPortfolioRepository.GetPositionById(999)).Returns(fakePosition);
};
Because of = () =>
{
_presenter.Initialize(_mockView, 1, 999);
};
It should_set_the_title_to_Edit_Position = () =>
Mock.Assert(() => _mockView.SetTitle("Edit Position"), Occurs.Exactly(1));
It should_prepopulate_the_PricePerShare_on_the_form = () =>
Mock.Assert(() => _mockView.PopulateForm(Arg.Matches<Position>(p => p.PricePerShare == 5.99M)), Occurs.Exactly(1));
It should_prepopulate_the_Shares_on_the_form = () =>
Mock.Assert(() => _mockView.PopulateForm(Arg.Matches<Position>(p => p.Shares == 50M)), Occurs.Exactly(1));
It should_prepopulate_the_Ticker_on_the_form = () =>
Mock.Assert(() => _mockView.PopulateForm(Arg.Matches<Position>(p => p.Ticker == "FAKE")), Occurs.Exactly(1));
}
public class When_the_user_wants_to_save_a_valid_position : EditPositionTests
{
Establish context = () =>
{
_presenter.Initialize(_mockView, 1);
Mock.Arrange(() => _mockStockService.IsValidTicker(Arg.AnyString)).Returns(true);
};
Because of = () =>
{
var fakeInputModel = new PositionInputModel {PriceText = "2.34", SharesText = "671", TickerText = "LOL"};
_presenter.Save(fakeInputModel);
};
It should_save_a_position_with_the_portfolio_repository = () =>
Mock.Assert(() => _mockPortfolioRepository.SavePosition(Arg.IsAny<Position>()), Occurs.Exactly(1));
It should_save_a_position_with_the_correct_Price = () =>
Mock.Assert(() => _mockPortfolioRepository.SavePosition(Arg.Matches<Position>(p => p.PricePerShare == 2.34M)), Occurs.Exactly(1));
It should_save_a_position_with_the_correct_Shares = () =>
Mock.Assert(() => _mockPortfolioRepository.SavePosition(Arg.Matches<Position>(p => p.Shares == 671M)), Occurs.Exactly(1));
It should_save_a_position_with_the_correct_Ticker = () =>
Mock.Assert(() => _mockPortfolioRepository.SavePosition(Arg.Matches<Position>(p => p.Ticker == "LOL")), Occurs.Exactly(1));
It should_save_a_position_with_the_correct_Containing_Portfolio_ID = () =>
Mock.Assert(() => _mockPortfolioRepository.SavePosition(Arg.Matches<Position>(p => p.ContainingPortfolioID == 1)), Occurs.Exactly(1));
It should_tell_the_view_to_go_back_to_the_main_activity = () =>
Mock.Assert(() => _mockView.GoBackToMainActivity(), Occurs.Exactly(1));
}
public class When_the_user_wants_to_save_an_invalid_position : EditPositionTests
{
Establish context = () =>
{
_presenter.Initialize(_mockView, 1);
Mock.Arrange(() => _mockStockService.IsValidTicker(Arg.AnyString)).Returns(false);
};
Because of = () =>
{
var fakeInputModel = new PositionInputModel {PriceText = "cows", SharesText = "WALRUS!!", TickerText = "fail"};
_presenter.Save(fakeInputModel);
};
It should_not_try_to_save_the_portfolio = () =>
Mock.Assert(() => _mockPortfolioRepository.SavePosition(Arg.IsAny<Position>()), Occurs.Never());
It should_send_the_validation_errors_to_the_view = () =>
Mock.Assert(() => _mockView.ShowErrorMessages(Arg.IsAny<IList<string>>()), Occurs.Exactly(1));
It should_send_an_invalid_ticker_error_to_the_view = () =>
MockAssertPositionMatches(x => x.Any(p => p == "Invalid Ticker Name"));
It should_send_an_invalid_shares_number_error_to_the_view = () =>
MockAssertPositionMatches(x => x.Any(p => p == "Please enter a valid, positive number of shares"));
It should_send_an_invalid_price_per_share_error_to_the_view = () =>
MockAssertPositionMatches(x => x.Any(p => p == "Please enter a valid, positive price per share"));
It should_not_tell_the_view_to_go_back_to_the_main_activity = () =>
Mock.Assert(() => _mockView.GoBackToMainActivity(), Occurs.Never());
private static void MockAssertPositionMatches(Expression<Predicate<IList<string>>> match)
{
Mock.Assert(() => _mockView.ShowErrorMessages(Arg.Matches(match)), Occurs.Exactly(1));
}
}
public class When_the_user_wants_to_save_an_invalid_position_with_blank_fields : EditPositionTests
{
Establish context = () =>
{
_presenter.Initialize(_mockView, 1);
Mock.Arrange(() => _mockStockService.IsValidTicker(Arg.AnyString)).Returns(false);
};
Because of = () =>
{
var fakeInputModel = new PositionInputModel { PriceText = "", SharesText = "", TickerText = "" };
_presenter.Save(fakeInputModel);
};
It should_not_try_to_save_the_portfolio = () =>
Mock.Assert(() => _mockPortfolioRepository.SavePosition(Arg.IsAny<Position>()), Occurs.Never());
It should_send_the_validation_errors_to_the_view = () =>
Mock.Assert(() => _mockView.ShowErrorMessages(Arg.IsAny<IList<string>>()), Occurs.Exactly(1));
It should_send_an_invalid_ticker_error_to_the_view = () =>
MockPositionMatches(x => x.Any(p => p == "Please enter a ticker"));
It should_send_an_invalid_shares_number_error_to_the_view = () =>
MockPositionMatches(x => x.Any(p => p == "Please enter a valid, positive number of shares"));
It should_send_an_invalid_price_per_share_error_to_the_view = () =>
MockPositionMatches(x => x.Any(p => p == "Please enter a valid, positive price per share"));
It should_not_tell_the_view_to_go_back_to_the_main_activity = () =>
Mock.Assert(() => _mockView.GoBackToMainActivity(), Occurs.Never());
private static void MockPositionMatches(Expression<Predicate<IList<string>>> match)
{
Mock.Assert(() => _mockView.ShowErrorMessages(Arg.Matches(match)), Occurs.Exactly(1));
}
}
}

View file

@ -0,0 +1,94 @@
using System.Collections.Generic;
using System.Linq;
using Machine.Specifications;
using MonoStockPortfolio.Activites.ConfigScreen;
using MonoStockPortfolio.Core.Config;
using MonoStockPortfolio.Entities;
using Telerik.JustMock;
using MonoStockPortfolio.Core;
namespace MonoStockPortfolio.Tests.Presenters
{
public class Given_a_Config_Presenter
{
protected static ConfigPresenter _presenter;
protected static IConfigRepository _configRepository;
protected static IConfigView _configView;
Establish context = () =>
{
_configView = Mock.Create<IConfigView>();
_configRepository = Mock.Create<IConfigRepository>();
Mock.Arrange(() => _configRepository.GetStockItems()).Returns(new List<StockDataItem>
{
StockDataItem.GainLoss,
StockDataItem.Change
});
_presenter = new ConfigPresenter(_configRepository);
};
}
public class When_initialize_the_config_presenter : Given_a_Config_Presenter
{
static List<StockDataItem> _allStockDataItems;
Establish context = () =>
{
_allStockDataItems = StockDataItem.Volume.GetValues<StockDataItem>().ToList();
};
Because of = () =>
{
_presenter.Initialize(_configView);
};
It should_send_two_checked_items = () =>
Mock.Assert(() =>
_configView.PrepopulateConfiguration(
Arg.IsAny<IList<StockDataItem>>(),
Arg.Matches<IEnumerable<StockDataItem>>(i => i.Count() == 2)),
Occurs.Exactly(1));
It should_send_GainLoss_as_a_checked_item = () =>
Mock.Assert(() =>
_configView.PrepopulateConfiguration(
Arg.IsAny<IList<StockDataItem>>(),
Arg.Matches<IEnumerable<StockDataItem>>(i => i.Any(p => p == StockDataItem.GainLoss))),
Occurs.Exactly(1));
It should_send_Change_as_a_checked_item = () =>
Mock.Assert(() =>
_configView.PrepopulateConfiguration(
Arg.IsAny<IList<StockDataItem>>(),
Arg.Matches<IEnumerable<StockDataItem>>(i => i.Any(p => p == StockDataItem.Change))),
Occurs.Exactly(1));
It should_send_an_enumerated_list_of_all_stock_items = () =>
Mock.Assert(() =>
_configView.PrepopulateConfiguration(
Arg.Matches<IList<StockDataItem>>(i => i.Count == _allStockDataItems.Count),
Arg.IsAny<IEnumerable<StockDataItem>>()),
Occurs.Exactly(1));
}
public class When_the_user_wants_to_save_configuration : Given_a_Config_Presenter
{
Establish context = () =>
{
_presenter.Initialize(_configView);
};
Because of = () =>
{
_presenter.SaveConfig(new List<StockDataItem> {StockDataItem.Ticker, StockDataItem.Time});
};
It should_use_the_repo_to_update_to_2_stock_items = () =>
Mock.Assert(() => _configRepository.UpdateStockItems(Arg.Matches<List<StockDataItem>>(i => i.Count == 2)));
It should_use_the_repo_to_update_stock_items_with_Time = () =>
Mock.Assert(() => _configRepository.UpdateStockItems(Arg.Matches<List<StockDataItem>>(i => i.Any(s => s == StockDataItem.Time))));
It should_use_the_repo_to_update_stock_items_with_Ticker = () =>
Mock.Assert(() => _configRepository.UpdateStockItems(Arg.Matches<List<StockDataItem>>(i => i.Any(s => s == StockDataItem.Ticker))));
}
}

View file

@ -0,0 +1,124 @@
using System.Collections.Generic;
using System.Linq;
using Machine.Specifications;
using MonoStockPortfolio.Activites.MainScreen;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Entities;
using Telerik.JustMock;
using Telerik.JustMock.Helpers;
namespace MonoStockPortfolio.Tests.Activities
{
public class Given_an_initialized_Main_Presenter
{
protected static IMainPresenter _presenter;
protected static IPortfolioRepository _mockPortfolioRepository;
protected static IMainView _mockView;
protected static IList<Portfolio> _portfolioList;
protected static Portfolio _portfolio1;
protected static Portfolio _portfolio2;
Establish context = () =>
{
_portfolio1 = new Portfolio(555) { Name = "portfolio1" };
_portfolio2 = new Portfolio(777) { Name = "portfolio2" };
_portfolioList = new List<Portfolio>();
_portfolioList.Add(_portfolio1);
_portfolioList.Add(_portfolio2);
_mockPortfolioRepository = Mock.Create<IPortfolioRepository>();
Mock.Arrange(() => _mockPortfolioRepository.GetAllPortfolios()).Returns(_portfolioList);
Mock.Arrange(() => _mockPortfolioRepository.GetPortfolioByName("portfolio1")).Returns(_portfolio1);
_mockView = Mock.Create<IMainView>();
_presenter = new MainPresenter(_mockPortfolioRepository);
_presenter.Initialize(_mockView);
};
}
public class When_initializing_the_Main_Presenter : Given_an_initialized_Main_Presenter
{
It should_get_the_portfolio_list = () =>
Mock.Assert(() => _mockPortfolioRepository.GetAllPortfolios(), Occurs.Exactly(1));
It should_refresh_the_view = () =>
Mock.Assert(() => _mockView.RefreshList(Arg.IsAny<IList<string>>()), Occurs.Exactly(1));
It should_refresh_the_view_with_the_portfolio_list = () =>
Mock.Assert(() => _mockView.RefreshList(Arg.Matches<IList<string>>(
stringList => stringList.SequenceEqual(_portfolioList.Select(p => p.Name))
)));
}
public class When_the_user_wants_to_add_a_new_portfolio : Given_an_initialized_Main_Presenter
{
Because of = () =>
_presenter.AddNewPortfolio();
It should_tell_the_view_to_bring_up_the_Add_new_portfolio_screen = () =>
Mock.Assert(() => _mockView.StartAddPortfolioActivity(), Occurs.Exactly(1));
}
public class When_the_user_wants_to_view_a_portfolio : Given_an_initialized_Main_Presenter
{
Because of = () =>
_presenter.ViewPortfolio(1);
It should_tell_the_view_to_bring_up_the_View_Portfolio_screen_with_the_given_position = () =>
{
var id = _portfolioList[1].ID ?? -1;
Mock.Assert(() => _mockView.StartViewPortfolioActivity(id), Occurs.Exactly(1));
};
}
public class When_the_user_wants_to_delete_a_portfolio : Given_an_initialized_Main_Presenter
{
Because of = () =>
_presenter.DeletePortfolio(990099);
It should_use_the_repo_to_delete_the_portfolio_with_the_given_ID = () =>
Mock.Assert(() => _mockPortfolioRepository.DeletePortfolioById(990099), Occurs.Exactly(1));
}
public class When_the_user_wants_to_edit_a_portfolio : Given_an_initialized_Main_Presenter
{
Because of = () =>
_presenter.EditPortfolio(909);
It should_tell_the_view_to_start_up_an_edit_activity_for_the_given_portfolio_id = () =>
Mock.Assert(() => _mockView.StartEditPortfolioActivity(909), Occurs.Exactly(1));
}
public class When_the_user_wants_to_configure_the_display_fields : Given_an_initialized_Main_Presenter
{
Because of = () =>
_presenter.GotoConfig();
It should_tell_the_view_to_start_up_the_config_activity = () =>
Mock.Assert(() => _mockView.StartConfigActivity(), Occurs.Exactly(1));
}
public class When_the_user_wants_to_exit_the_app : Given_an_initialized_Main_Presenter
{
Because of = () =>
_presenter.ExitApplication();
It should_tell_the_view_to_start_up_the_config_activity = () =>
Mock.Assert(() => _mockView.ExitApplication(), Occurs.Exactly(1));
}
public class When_the_user_wants_to_see_the_context_menu : Given_an_initialized_Main_Presenter
{
static int _id;
Because of = () =>
_id = _presenter.GetPortfolioIdForContextMenu(_portfolio1.Name);
It should_use_the_given_name_to_lookup_the_ID = () =>
{
Mock.Assert(() => _mockPortfolioRepository.GetPortfolioByName(_portfolio1.Name), Occurs.Exactly(1));
_portfolio1.ID.ShouldEqual(_id);
};
}
}

View file

@ -0,0 +1,107 @@
using System.Collections.Generic;
using System.Threading;
using Machine.Specifications;
using MonoStockPortfolio.Activites.PortfolioScreen;
using MonoStockPortfolio.Core.Config;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Core.Services;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Framework;
using Telerik.JustMock;
using Telerik.JustMock.Helpers;
namespace MonoStockPortfolio.Tests.Presenters
{
public class Given_an_initialized_Portfolio_Presenter
{
protected static IPortfolioPresenter _presenter;
protected static IPortfolioRepository _mockPortfolioRepository;
protected static IPortfolioService _mockPortfolioService;
protected static IConfigRepository _mockConfigRepository;
protected static IPortfolioView _mockView;
Establish context = () =>
{
OnWorkerThreadAttribute.ThreadingService = new DoNotThreadService();
_mockPortfolioRepository = Mock.Create<IPortfolioRepository>();
Mock.Arrange(() => _mockPortfolioRepository.GetPortfolioById(999)).Returns(new Portfolio(123) { Name = "Test Portfolio" });
_mockPortfolioService = Mock.Create<IPortfolioService>();
Mock.Arrange(() => _mockPortfolioService.GetDetailedItems(999, Arg.IsAny<IEnumerable<StockDataItem>>())).
Returns(new List<PositionResultsViewModel> { new PositionResultsViewModel(123) });
_mockConfigRepository = Mock.Create<IConfigRepository>();
_mockView = Mock.Create<IPortfolioView>();
_presenter = new PortfolioPresenter(_mockPortfolioRepository, _mockPortfolioService, _mockConfigRepository);
_presenter.Initialize(_mockView, 999);
};
}
internal class DoNotThreadService : IThreadingService
{
public void QueueUserWorkItem(WaitCallback func)
{
func.Invoke(null);
}
}
public class When_done_initializing_a_Portfolio_Presenter : Given_an_initialized_Portfolio_Presenter
{
It should_show_the_progress_bar_with_a_nice_message = () =>
Mock.Assert(() => _mockView.ShowProgressDialog("Loading...Please wait..."), Occurs.Exactly(1));
It should_use_the_service_to_get_the_positions = () =>
Mock.Assert(() => _mockPortfolioService.GetDetailedItems(999, Arg.IsAny<IEnumerable<StockDataItem>>()), Occurs.Exactly(1));
It should_hide_the_progress_bar_message = () =>
Mock.Assert(() => _mockView.HideProgressDialog(), Occurs.Exactly(1));
It should_refresh_the_view = () =>
Mock.Assert(() => _mockView.RefreshList(Arg.IsAny<IEnumerable<PositionResultsViewModel>>(), Arg.IsAny<IEnumerable<StockDataItem>>()), Occurs.Exactly(1));
It should_get_the_portfolio_name_from_the_repository_and_set_the_title_with_that = () =>
Mock.Assert(() => _mockView.SetTitle("Portfolio: Test Portfolio"), Occurs.Exactly(1));
}
public class When_the_user_wants_to_add_a_new_position : Given_an_initialized_Portfolio_Presenter
{
Because of = () =>
_presenter.AddNewPosition();
It should_tell_the_view_to_bring_up_the_Add_new_portfolio_screen = () =>
Mock.Assert(() => _mockView.StartAddNewPosition(999), Occurs.Exactly(1));
}
public class When_the_user_selects_edit_context_option : Given_an_initialized_Portfolio_Presenter
{
Because of = () =>
_presenter.ContextOptionSelected("Edit", 123);
It should_bring_up_the_edit_screen = () =>
Mock.Assert(() => _mockView.StartEditPosition(123, 999), Occurs.Exactly(1));
}
public class When_the_user_selects_delete_context_option : Given_an_initialized_Portfolio_Presenter
{
Because of = () =>
_presenter.ContextOptionSelected("Delete", 123);
It should_use_the_repo_to_delete_the_position = () =>
Mock.Assert(() => _mockPortfolioRepository.DeletePositionById(123), Occurs.Exactly(1));
}
public class When_the_user_wants_to_refresh_the_positions : Given_an_initialized_Portfolio_Presenter
{
Because of = () =>
_presenter.RefreshPositions();
It should_show_the_progress_bar_with_a_nice_message_again = () =>
Mock.Assert(() => _mockView.ShowProgressDialog("Loading...Please wait..."), Occurs.Exactly(2));
It should_use_the_service_to_get_the_positions_again = () =>
Mock.Assert(() => _mockPortfolioService.GetDetailedItems(999, Arg.IsAny<IEnumerable<StockDataItem>>()), Occurs.Exactly(2));
It should_hide_the_progress_bar_message_again = () =>
Mock.Assert(() => _mockView.HideProgressDialog(), Occurs.Exactly(2));
It should_refresh_the_view_again = () =>
Mock.Assert(() => _mockView.RefreshList(Arg.IsAny<IEnumerable<PositionResultsViewModel>>(), Arg.IsAny<IEnumerable<StockDataItem>>()), Occurs.Exactly(2));
}
}

View file

@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Linq;
using Machine.Specifications;
using Machine.Specifications.Runner.Impl;
using MonoStockPortfolio.Core.StockData;
using MonoStockPortfolio.Entities;
namespace MonoStockPortfolio.Tests.Services
{
public class When_using_the_Yahoo_stock_data_service_to_get_quotes
{
static YahooStockDataProvider _svc;
static IList<StockQuote> _quotes;
Establish context = () =>
{
_svc = new YahooStockDataProvider();
};
Because of = () =>
{
_quotes = _svc.GetStockQuotes(new[] { "GOOG", "AMZN", "AAPL", "MSFT", "NOVL", "S", "VZ", "T" })
.ToList();
};
It should_get_volumes_from_the_web = () =>
_quotes.ForEach(q => string.IsNullOrEmpty(q.Volume).ShouldBeFalse());
It should_get_last_trade_prices_from_the_web = () =>
_quotes.ForEach(q => q.LastTradePrice.ShouldNotEqual(0.0M));
It should_get_price_change_from_the_web = () =>
_quotes.ForEach(q => q.Change.ShouldNotEqual(0.0M));
}
public class When_using_the_Yahoo_stock_data_service_to_validate_tickers
{
static YahooStockDataProvider _svc;
static bool _goodTicker;
static bool _badTicker;
Establish context = () =>
{
_svc = new YahooStockDataProvider();
};
Because of = () =>
{
_goodTicker = _svc.IsValidTicker("GOOG");
_badTicker = _svc.IsValidTicker("GOOGAMOOGA");
};
It should_validate_the_good_ticker = () =>
_goodTicker.ShouldBeTrue();
It shouldnt_validate_the_bad_ticker = () =>
_badTicker.ShouldBeFalse();
}
}

View file

@ -1,109 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Core.Services;
using MonoStockPortfolio.Core.StockData;
using MonoStockPortfolio.Entities;
using Xunit;
namespace MonoStockPortfolio.Tests
{
public class YahooStockDataProviderTests
{
[Fact]
public void Can_get_volume_from_web()
{
var svc = new YahooStockDataProvider();
var quotes = svc.GetStockQuotes(new[] {"GOOG", "AMZN", "AAPL", "MSFT", "NOVL", "S", "VZ", "T"});
foreach (var stockQuote in quotes)
{
Assert.True(!string.IsNullOrEmpty(stockQuote.Volume));
}
Assert.True(quotes.Any());
}
[Fact]
public void Can_get_volume_from_service()
{
var svc = new PortfolioService(BuildStubPortfolioRepo(), new YahooStockDataProvider());
var items = svc.GetDetailedItems(1, new List<StockDataItem> {StockDataItem.Ticker, StockDataItem.Volume});
foreach (var positionResultsViewModel in items)
{
Assert.True(positionResultsViewModel.Items[StockDataItem.Volume] != null);
}
Assert.True(items.Any());
}
[Fact]
public void Can_tell_if_a_ticker_is_valid()
{
var svc = new YahooStockDataProvider();
Assert.True(svc.IsValidTicker("GOOG"));
}
[Fact]
public void Can_tell_if_a_ticker_is_invalid()
{
var svc = new YahooStockDataProvider();
Assert.False(svc.IsValidTicker("HARBLHARBLHARBL"));
}
private IPortfolioRepository BuildStubPortfolioRepo()
{
return new StubPortfolioRepo();
}
public class StubPortfolioRepo : IPortfolioRepository
{
public IList<Position> GetAllPositions(long portfolioId)
{
return new List<Position>
{
new Position(1)
{ContainingPortfolioID = 1, PricePerShare = 5, Shares = 100, Ticker = "GOOG"}
};
}
public IList<Portfolio> GetAllPortfolios()
{
throw new NotImplementedException();
}
public void SavePortfolio(Portfolio portfolio)
{
throw new NotImplementedException();
}
public void DeletePortfolioById(int portfolioId)
{
throw new NotImplementedException();
}
public Portfolio GetPortfolioById(long portfolioId)
{
throw new NotImplementedException();
}
public Portfolio GetPortfolioByName(string portfolioName)
{
throw new NotImplementedException();
}
public void SavePosition(Position position)
{
throw new NotImplementedException();
}
public void DeletePositionById(long positionId)
{
throw new NotImplementedException();
}
public Position GetPositionById(long positionId)
{
throw new NotImplementedException();
}
}
}
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Machine.Specifications" version="0.4.8.0" />
</packages>

View file

@ -6,7 +6,6 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libs", "libs", "{643BA3D4-E3D6-49B7-B347-2B935FD5B9FC}"
ProjectSection(SolutionItems) = preProject
libs\PostSharp.SL.dll = libs\PostSharp.SL.dll
libs\xunit.dll = libs\xunit.dll
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoStockPortfolio.Core", "MonoStockPortfolio.Core\MonoStockPortfolio.Core.csproj", "{251E7BB4-CFE2-4DE4-9E2A-AAE1AF41C8CB}"
@ -15,7 +14,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoStockPortfolio.Entities
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoStockPortfolio.Tests", "MonoStockPortfolio.Tests\MonoStockPortfolio.Tests.csproj", "{C2797FAB-AFAB-49F6-9131-FC9BF03CAB9D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoDroid.LumenWorks.Framework.IO.Csv", "MonoDroid.FileHelpers\MonoDroid.LumenWorks.Framework.IO.Csv.csproj", "{1AAA2739-D853-41B0-866B-B55B373616E1}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoDroid.LumenWorks.Framework.IO.Csv", "MonoDroid.LumenWorks.Framework.IO.Csv\MonoDroid.LumenWorks.Framework.IO.Csv.csproj", "{1AAA2739-D853-41B0-866B-B55B373616E1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View file

@ -4,61 +4,19 @@ using Android.App;
using Android.Content;
using Android.OS;
using Android.Widget;
using MonoStockPortfolio.Core.Config;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Framework;
using MonoStockPortfolio.Core;
namespace MonoStockPortfolio.Activites
namespace MonoStockPortfolio.Activites.ConfigScreen
{
[Activity(Label = "Config", Name = "monostockportfolio.activites.ConfigActivity")]
public class ConfigActivity : Activity
public class ConfigActivity : Activity, IConfigView
{
[IoC] private IConfigRepository _repo;
[LazyView(Resource.Id.configList)] private ListView ConfigList;
[LazyView(Resource.Id.btnSaveConfig)] private Button SaveConfigButton;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.config);
var allitems = StockDataItem.Volume.GetValues<StockDataItem>().ToList();
var allitemsLabels = allitems.Select(i => i.GetStringValue()).ToList();
var checkeditems = _repo.GetStockItems();
var configAdapter = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleListItemMultipleChoice, allitemsLabels);
ConfigList.Adapter = configAdapter;
ConfigList.ChoiceMode = ChoiceMode.Multiple;
for(int i=0;i<ConfigList.Count;i++)
{
if (checkeditems.Contains(allitems[i]))
{
ConfigList.SetItemChecked(i, true);
}
}
SaveConfigButton.Click += SaveConfigButton_Click;
}
void SaveConfigButton_Click(object sender, System.EventArgs e)
{
var allitems = StockDataItem.Volume.GetValues<StockDataItem>().ToList();
var newConfig = new List<StockDataItem>();
for (int i = 0; i < ConfigList.Count; i++)
{
if(ConfigList.IsItemChecked(i))
{
newConfig.Add(allitems[i]);
}
}
_repo.UpdateStockItems(newConfig);
this.LongToast("Configuration updated!");
}
[IoC] IConfigPresenter _presenter;
public static Intent GotoIntent(Context context)
{
@ -66,5 +24,59 @@ namespace MonoStockPortfolio.Activites
intent.SetClassName(context, ManifestNames.GetName<ConfigActivity>());
return intent;
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.config);
_presenter.Initialize(this);
WireUpEvents();
}
void WireUpEvents()
{
SaveConfigButton.Click += SaveConfigButton_Click;
}
#region IConfigView members
public void PrepopulateConfiguration(IList<StockDataItem> allitems, IEnumerable<StockDataItem> checkeditems)
{
var allitemsLabels = allitems.Select(i => i.GetStringValue()).ToList();
var configAdapter = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleListItemMultipleChoice, allitemsLabels);
ConfigList.Adapter = configAdapter;
ConfigList.ChoiceMode = ChoiceMode.Multiple;
for (int i = 0; i < ConfigList.Count; i++)
{
if (checkeditems.Contains(allitems[i]))
{
ConfigList.SetItemChecked(i, true);
}
}
}
public void ShowToastMessage(string message)
{
this.LongToast(message);
}
#endregion
void SaveConfigButton_Click(object sender, System.EventArgs e)
{
var checkedItems = new List<StockDataItem>();
for(int i =0;i<ConfigList.Count;i++)
{
if (ConfigList.IsItemChecked(i))
{
checkedItems.Add((StockDataItem) i);
}
}
_presenter.SaveConfig(checkedItems);
}
}
}

View file

@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Linq;
using MonoStockPortfolio.Core.Config;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Core;
namespace MonoStockPortfolio.Activites.ConfigScreen
{
public class ConfigPresenter : IConfigPresenter
{
private IConfigView _currentView;
private readonly IConfigRepository _configRepository;
public ConfigPresenter(IConfigRepository configRepository)
{
_configRepository = configRepository;
}
public void Initialize(IConfigView configView)
{
_currentView = configView;
var allitems = StockDataItem.Volume.GetValues<StockDataItem>().ToList();
var checkeditems = _configRepository.GetStockItems();
_currentView.PrepopulateConfiguration(allitems, checkeditems);
}
public void SaveConfig(List<StockDataItem> checkedItems)
{
_configRepository.UpdateStockItems(checkedItems);
_currentView.ShowToastMessage("Configuration updated!");
}
}
}

View file

@ -0,0 +1,11 @@
using System.Collections.Generic;
using MonoStockPortfolio.Entities;
namespace MonoStockPortfolio.Activites.ConfigScreen
{
public interface IConfigPresenter
{
void Initialize(IConfigView configView);
void SaveConfig(List<StockDataItem> checkedItems);
}
}

View file

@ -0,0 +1,11 @@
using System.Collections.Generic;
using MonoStockPortfolio.Entities;
namespace MonoStockPortfolio.Activites.ConfigScreen
{
public interface IConfigView
{
void PrepopulateConfiguration(IList<StockDataItem> allitems, IEnumerable<StockDataItem> checkeditems);
void ShowToastMessage(string message);
}
}

View file

@ -1,18 +1,18 @@
using System;
using System.Collections.Generic;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Widget;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Framework;
namespace MonoStockPortfolio.Activites
namespace MonoStockPortfolio.Activites.EditPortfolioScreen
{
[Activity(Label = "Add Portfolio", MainLauncher = false, Name = "monostockportfolio.activites.EditPortfolioActivity")]
public class EditPortfolioActivity : Activity
public class EditPortfolioActivity : Activity, IEditPortfolioView
{
[IoC] private IPortfolioRepository _repo;
[IoC] private IEditPortfolioPresenter _presenter;
[LazyView(Resource.Id.btnSave)] protected Button SaveButton;
[LazyView(Resource.Id.portfolioName)] protected EditText PortfolioName;
@ -32,7 +32,41 @@ namespace MonoStockPortfolio.Activites
intent.PutExtra(PORTFOLIOIDEXTRA, portfolioId);
return intent;
}
public long ThisPortfolioId { get { return Intent.GetLongExtra(PORTFOLIOIDEXTRA, -1); } }
#region IEditPortfolioView members
public void SetTitle(string title)
{
this.Title = title;
}
public void PopulateForm(Portfolio portfolio)
{
PortfolioName.Text = portfolio.Name;
}
public void ShowSaveSuccessMessage(string message)
{
this.LongToast(message);
}
public void GoBackToMainActivity()
{
this.EndActivity();
}
public void ShowValidationErrors(IEnumerable<string> errors)
{
var errorMessage = string.Empty;
foreach (var error in errors)
{
errorMessage += error + "\n";
}
errorMessage = errorMessage.Trim('\n');
this.LongToast(errorMessage);
}
#endregion
protected override void OnCreate(Bundle bundle)
{
@ -40,20 +74,17 @@ namespace MonoStockPortfolio.Activites
SetContentView(Resource.Layout.addportfolio);
WireUpEvents();
var portfolioId = ThisPortfolioId;
if(portfolioId != -1)
var portfolioId = Intent.GetLongExtra(PORTFOLIOIDEXTRA, -1);
if (portfolioId != -1)
{
this.Title = "Edit Portfolio";
PopulateForm(portfolioId);
_presenter.Initialize(this, portfolioId);
}
else
{
_presenter.Initialize(this);
}
}
private void PopulateForm(long portfolioId)
{
var portfolio = _repo.GetPortfolioById(portfolioId);
PortfolioName.Text = portfolio.Name;
WireUpEvents();
}
private void WireUpEvents()
@ -63,47 +94,13 @@ namespace MonoStockPortfolio.Activites
private void saveButton_Click(object sender, EventArgs e)
{
Portfolio portfolioToSave = GetPortfolioToSave();
if (Validate(portfolioToSave))
{
_repo.SavePortfolio(portfolioToSave);
this.LongToast("You saved: " + PortfolioName.Text);
this.EndActivity();
}
}
private bool Validate(Portfolio portfolioToSave)
{
var validator = new FormValidator();
validator.AddRequired(PortfolioName, "Please enter a portfolio name");
validator.AddValidation(PortfolioName, () => IsDuplicateName(portfolioToSave));
var result = validator.Apply();
if(result != string.Empty)
{
this.LongToast(result);
return false;
}
return true;
}
private string IsDuplicateName(Portfolio portfolioToSave)
{
var portfolio = _repo.GetPortfolioByName(portfolioToSave.Name);
if(portfolio != null && portfolio.ID != portfolioToSave.ID)
{
return "Portfolio name is already taken";
}
return string.Empty;
_presenter.SavePortfolio(GetPortfolioToSave());
}
private Portfolio GetPortfolioToSave()
{
Portfolio portfolioToSave;
var portfolioId = ThisPortfolioId;
var portfolioId = Intent.GetLongExtra(PORTFOLIOIDEXTRA, -1);
if (portfolioId != -1)
{
portfolioToSave = new Portfolio(portfolioId);
@ -112,7 +109,7 @@ namespace MonoStockPortfolio.Activites
{
portfolioToSave = new Portfolio();
}
portfolioToSave.Name = PortfolioName.Text.ToString();
portfolioToSave.Name = PortfolioName.Text;
return portfolioToSave;
}
}

View file

@ -0,0 +1,80 @@
using System.Collections.Generic;
using System.Linq;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Framework;
namespace MonoStockPortfolio.Activites.EditPortfolioScreen
{
public class EditPortfolioPresenter : IEditPortfolioPresenter
{
private IEditPortfolioView _currentView;
private long? _portfolioId;
private readonly IPortfolioRepository _porfolioRepository;
public EditPortfolioPresenter(IPortfolioRepository portfolioRepository)
{
_porfolioRepository = portfolioRepository;
}
public void Initialize(IEditPortfolioView editPortfolioView, long? portfolioId = null)
{
_portfolioId = portfolioId;
_currentView = editPortfolioView;
SetTitle();
PrepopulateForm();
}
public void SavePortfolio(Portfolio portfolioToSave)
{
var errors = Validate((portfolioToSave));
if (!errors.Any())
{
_porfolioRepository.SavePortfolio(portfolioToSave);
_currentView.ShowSaveSuccessMessage("You saved: " + portfolioToSave.Name);
_currentView.GoBackToMainActivity();
}
else
{
_currentView.ShowValidationErrors(errors);
}
}
private IEnumerable<string> Validate(Portfolio portfolioToSave)
{
var validator = new FormValidator();
validator.AddRequired(() => portfolioToSave.Name, "Please enter a portfolio name");
validator.AddValidation(() => IsDuplicateName(portfolioToSave));
return validator.Apply().ToList();
}
private string IsDuplicateName(Portfolio portfolioToSave)
{
var portfolio = _porfolioRepository.GetPortfolioByName(portfolioToSave.Name);
if (portfolio != null && portfolio.ID != portfolioToSave.ID)
{
return "Portfolio name is already taken";
}
return string.Empty;
}
private void PrepopulateForm()
{
if (_portfolioId != null)
{
var portfolio = _porfolioRepository.GetPortfolioById(_portfolioId ?? -1);
_currentView.PopulateForm(portfolio);
}
}
private void SetTitle()
{
_currentView.SetTitle(_portfolioId == null ? "Add New Portfolio" : "Edit Portfolio");
}
}
}

View file

@ -0,0 +1,10 @@
using MonoStockPortfolio.Entities;
namespace MonoStockPortfolio.Activites.EditPortfolioScreen
{
public interface IEditPortfolioPresenter
{
void Initialize(IEditPortfolioView editPortfolioView, long? portfolioId = null);
void SavePortfolio(Portfolio portfolioToSave);
}
}

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
using MonoStockPortfolio.Entities;
namespace MonoStockPortfolio.Activites.EditPortfolioScreen
{
public interface IEditPortfolioView
{
void SetTitle(string title);
void PopulateForm(Portfolio portfolio);
void ShowSaveSuccessMessage(string message);
void GoBackToMainActivity();
void ShowValidationErrors(IEnumerable<string> errors);
}
}

View file

@ -1,140 +0,0 @@
using System;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Widget;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Core.StockData;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Framework;
namespace MonoStockPortfolio.Activites
{
[Activity(Label = "Add Position", MainLauncher = false, Name = "monostockportfolio.activites.EditPositionActivity")]
public class EditPositionActivity : Activity
{
[IoC] private IPortfolioRepository _repo;
[IoC] private IStockDataProvider _svc;
[LazyView(Resource.Id.addPositionTicker)] protected EditText TickerTextBox;
[LazyView(Resource.Id.addPositionPrice)] protected EditText PriceTextBox;
[LazyView(Resource.Id.addPositionShares)] protected EditText SharesTextBox;
[LazyView(Resource.Id.addPositionSaveButton)] protected Button SaveButton;
private const string POSITIONIDEXTRA = "monoStockPortfolio.EditPositionActivity.PositionID";
private const string PORTFOLIOIDEXTRA = "monoStockPortfolio.EditPositionActivity.PortfolioID";
public static Intent AddIntent(Context context, long portfolioId)
{
var intent = new Intent();
intent.SetClassName(context, ManifestNames.GetName<EditPositionActivity>());
intent.PutExtra(PORTFOLIOIDEXTRA, portfolioId);
return intent;
}
public static Intent EditIntent(Context context, long positionId, long portfolioId)
{
var intent = new Intent();
intent.SetClassName(context, ManifestNames.GetName<EditPositionActivity>());
intent.PutExtra(POSITIONIDEXTRA, positionId);
intent.PutExtra(PORTFOLIOIDEXTRA, portfolioId);
return intent;
}
public long ThisPortfolioId { get { return Intent.GetLongExtra(PORTFOLIOIDEXTRA, -1); } }
public long ThisPositionId { get { return Intent.GetLongExtra(POSITIONIDEXTRA, -1); } }
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.addposition);
var positionId = ThisPositionId;
if (positionId != -1)
{
this.Title = "Edit Position";
PopulateForm(positionId);
}
WireUpEvents();
}
private void PopulateForm(long positionId)
{
var position = _repo.GetPositionById(positionId);
this.TickerTextBox.Text = position.Ticker;
this.PriceTextBox.Text = position.PricePerShare.ToString();
this.SharesTextBox.Text = position.Shares.ToString();
}
private void WireUpEvents()
{
SaveButton.Click += saveButton_Click;
}
void saveButton_Click(object sender, EventArgs e)
{
if(Validate())
{
var positionToSave = GetPositionToSave();
_repo.SavePosition(positionToSave);
this.EndActivity();
}
}
private bool Validate()
{
var result = ValidationRules.Apply();
if (result == string.Empty)
{
return true;
}
this.LongToast(result);
return false;
}
private FormValidator ValidationRules
{
get
{
var validator = new FormValidator();
validator.AddRequired(TickerTextBox, "Please enter a ticker");
validator.AddValidPositiveDecimal(SharesTextBox, "Please enter a valid, positive number of shares");
validator.AddValidPositiveDecimal(PriceTextBox, "Please enter a valid, positive price per share");
validator.AddValidation(TickerTextBox, () => ValidateTicker(TickerTextBox.Text));
return validator;
}
}
private string ValidateTicker(string ticker)
{
if(_svc.IsValidTicker(ticker))
{
return string.Empty;
}
return "Invalid Ticker Name";
}
private Position GetPositionToSave()
{
Position positionToSave;
var positionId = ThisPositionId;
if (positionId != -1)
{
positionToSave = new Position(positionId);
}
else
{
positionToSave = new Position();
}
positionToSave.Shares = decimal.Parse(SharesTextBox.Text);
positionToSave.PricePerShare = decimal.Parse(PriceTextBox.Text);
positionToSave.Ticker = TickerTextBox.Text.ToUpper();
positionToSave.ContainingPortfolioID = ThisPortfolioId;
return positionToSave;
}
}
}

View file

@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Widget;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Framework;
namespace MonoStockPortfolio.Activites.EditPositionScreen
{
[Activity(Label = "Add Position", MainLauncher = false, Name = "monostockportfolio.activites.EditPositionActivity")]
public class EditPositionActivity : Activity, IEditPositionView
{
[IoC] IEditPositionPresenter _presenter;
[LazyView(Resource.Id.addPositionTicker)] protected EditText TickerTextBox;
[LazyView(Resource.Id.addPositionPrice)] protected EditText PriceTextBox;
[LazyView(Resource.Id.addPositionShares)] protected EditText SharesTextBox;
[LazyView(Resource.Id.addPositionSaveButton)] protected Button SaveButton;
private const string POSITIONIDEXTRA = "monoStockPortfolio.EditPositionActivity.PositionID";
private const string PORTFOLIOIDEXTRA = "monoStockPortfolio.EditPositionActivity.PortfolioID";
public static Intent AddIntent(Context context, long portfolioId)
{
var intent = new Intent();
intent.SetClassName(context, ManifestNames.GetName<EditPositionActivity>());
intent.PutExtra(PORTFOLIOIDEXTRA, portfolioId);
return intent;
}
public static Intent EditIntent(Context context, long positionId, long portfolioId)
{
var intent = new Intent();
intent.SetClassName(context, ManifestNames.GetName<EditPositionActivity>());
intent.PutExtra(POSITIONIDEXTRA, positionId);
intent.PutExtra(PORTFOLIOIDEXTRA, portfolioId);
return intent;
}
#region IEditPositionView implementation
public void SetTitle(string title)
{
this.Title = title;
}
public void PopulateForm(Position position)
{
this.TickerTextBox.Text = position.Ticker;
this.PriceTextBox.Text = position.PricePerShare.ToString();
this.SharesTextBox.Text = position.Shares.ToString();
}
public void GoBackToMainActivity()
{
this.EndActivity();
}
public void ShowErrorMessages(IList<string> errorMessages)
{
var errorMessage = string.Empty;
foreach (var error in errorMessages)
{
errorMessage += error + "\n";
}
errorMessage = errorMessage.Trim('\n');
this.LongToast(errorMessage);
}
#endregion
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.addposition);
var portfolioId = Intent.GetLongExtra(PORTFOLIOIDEXTRA, -1);
var positionId = Intent.GetLongExtra(POSITIONIDEXTRA, -1);
if (positionId != -1)
{
_presenter.Initialize(this, portfolioId, positionId);
}
else
{
_presenter.Initialize(this, portfolioId);
}
WireUpEvents();
}
private void WireUpEvents()
{
SaveButton.Click += saveButton_Click;
}
void saveButton_Click(object sender, EventArgs e)
{
_presenter.Save(GetPositionInputModel());
}
private PositionInputModel GetPositionInputModel()
{
var model = new PositionInputModel();
model.TickerText = this.TickerTextBox.Text;
model.PriceText = this.PriceTextBox.Text;
model.SharesText = this.SharesTextBox.Text;
return model;
}
}
}

View file

@ -0,0 +1,89 @@
using System.Linq;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Core.StockData;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Framework;
namespace MonoStockPortfolio.Activites.EditPositionScreen
{
public class EditPositionPresenter : IEditPositionPresenter
{
private IEditPositionView _currentView;
private readonly IPortfolioRepository _portfolioRepository;
private readonly IStockDataProvider _stockService;
private long? _positionId;
private long _portfolioId;
public EditPositionPresenter(IPortfolioRepository portfolioRepository, IStockDataProvider stockService)
{
_portfolioRepository = portfolioRepository;
_stockService = stockService;
}
public void Initialize(IEditPositionView editPositionView, long portfolioId, long? positionId = null)
{
_currentView = editPositionView;
_positionId = positionId;
_portfolioId = portfolioId;
if (positionId != null)
{
_currentView.SetTitle("Edit Position");
var position = _portfolioRepository.GetPositionById(positionId ?? -1);
_currentView.PopulateForm(position);
}
else
{
_currentView.SetTitle("Add Position");
}
}
public void Save(PositionInputModel positionInputModel)
{
var validator = new FormValidator();
validator.AddRequired(() => positionInputModel.TickerText, "Please enter a ticker");
validator.AddValidPositiveDecimal(() => positionInputModel.SharesText, "Please enter a valid, positive number of shares");
validator.AddValidPositiveDecimal(() => positionInputModel.PriceText, "Please enter a valid, positive price per share");
validator.AddValidation(() => ValidateTicker(positionInputModel.TickerText));
var errorMessages = validator.Apply();
if (!errorMessages.Any())
{
_portfolioRepository.SavePosition(GetPosition(positionInputModel));
_currentView.GoBackToMainActivity();
}
else
{
_currentView.ShowErrorMessages(errorMessages.ToList());
}
}
private Position GetPosition(PositionInputModel positionInputModel)
{
Position positionToSave;
if (_positionId != null)
{
positionToSave = new Position(_positionId ?? -1);
}
else
{
positionToSave = new Position();
}
positionToSave.Shares = decimal.Parse(positionInputModel.SharesText);
positionToSave.PricePerShare = decimal.Parse(positionInputModel.PriceText);
positionToSave.Ticker = positionInputModel.TickerText.ToUpper();
positionToSave.ContainingPortfolioID = _portfolioId;
return positionToSave;
}
private string ValidateTicker(string ticker)
{
if (_stockService.IsValidTicker(ticker))
{
return string.Empty;
}
return "Invalid Ticker Name";
}
}
}

View file

@ -0,0 +1,8 @@
namespace MonoStockPortfolio.Activites.EditPositionScreen
{
public interface IEditPositionPresenter
{
void Initialize(IEditPositionView editPositionActivity, long portfolioId, long? positionId = null);
void Save(PositionInputModel getPositionInputModel);
}
}

View file

@ -0,0 +1,13 @@
using System.Collections.Generic;
using MonoStockPortfolio.Entities;
namespace MonoStockPortfolio.Activites.EditPositionScreen
{
public interface IEditPositionView
{
void SetTitle(string title);
void PopulateForm(Position position);
void GoBackToMainActivity();
void ShowErrorMessages(IList<string> errorMessages);
}
}

View file

@ -0,0 +1,9 @@
namespace MonoStockPortfolio.Activites.EditPositionScreen
{
public class PositionInputModel
{
public string TickerText { get; set; }
public string PriceText { get; set; }
public string SharesText { get; set; }
}
}

View file

@ -0,0 +1,15 @@
namespace MonoStockPortfolio.Activites.MainScreen
{
public interface IMainPresenter
{
void Initialize(IMainView view);
void RefreshPortfolioList();
void AddNewPortfolio();
void ViewPortfolio(int portfolioPosition);
void DeletePortfolio(int itemId);
void EditPortfolio(int itemId);
void GotoConfig();
void ExitApplication();
int GetPortfolioIdForContextMenu(string selectedPortfolioName);
}
}

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace MonoStockPortfolio.Activites.MainScreen
{
public interface IMainView
{
void RefreshList(IList<string> portfolioNames);
void StartAddPortfolioActivity();
void StartViewPortfolioActivity(long portfolioId);
void StartEditPortfolioActivity(int itemId);
void StartConfigActivity();
void ExitApplication();
}
}

View file

@ -1,26 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Views;
using Android.Widget;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Activites.ConfigScreen;
using MonoStockPortfolio.Activites.EditPortfolioScreen;
using MonoStockPortfolio.Activites.PortfolioScreen;
using MonoStockPortfolio.Framework;
namespace MonoStockPortfolio.Activites
namespace MonoStockPortfolio.Activites.MainScreen
{
[Activity(Label = "Stock Portfolio", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity
[Activity(Label = "Stock Portfolio", MainLauncher = true, Icon = "@drawable/icon", Name = "monostockportfolio.activites.mainscreen.MainActivity")]
public class MainActivity : Activity, IMainView
{
[IoC] private IPortfolioRepository _repo;
[LazyView(Resource.Id.btnAddPortfolio)] protected Button AddPortfolioButton;
[LazyView(Resource.Id.portfolioList)] protected ListView PortfolioListView;
private IList<Portfolio> _portfolios;
[IoC] IMainPresenter _presenter;
protected override void OnCreate(Bundle bundle)
{
@ -28,19 +26,50 @@ namespace MonoStockPortfolio.Activites
SetContentView(Resource.Layout.main);
RefreshList();
_presenter.Initialize(this);
WireUpEvents();
}
private void RefreshList()
{
_portfolios = _repo.GetAllPortfolios();
#region IMainView implementation
var listAdapter = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleListItem1, _portfolios.Select(p => p.Name).ToList());
public void RefreshList(IList<string> portfolioNames)
{
var listAdapter = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleListItem1, portfolioNames);
PortfolioListView.Adapter = listAdapter;
}
public void ExitApplication()
{
Finish();
}
public void StartEditPortfolioActivity(int itemId)
{
var intent = EditPortfolioActivity.EditIntent(this, itemId);
StartActivityForResult(intent, 0);
}
public void StartConfigActivity()
{
var intent = ConfigActivity.GotoIntent(this);
StartActivityForResult(intent, 0);
}
public void StartViewPortfolioActivity(long portfolioId)
{
var intent = PortfolioActivity.ViewIntent(this, portfolioId);
StartActivityForResult(intent, 0);
}
public void StartAddPortfolioActivity()
{
var intent = EditPortfolioActivity.AddIntent(this);
StartActivityForResult(intent, 0);
}
#endregion
private void WireUpEvents()
{
AddPortfolioButton.Click += addPortfolioButton_Click;
@ -54,8 +83,8 @@ namespace MonoStockPortfolio.Activites
var info = (AdapterView.AdapterContextMenuInfo)menuInfo;
var selectedPortfolioName = ((TextView)info.TargetView).Text.ToS();
var selectedPortfolio = _repo.GetPortfolioByName(selectedPortfolioName);
var id = (int)(selectedPortfolio.ID ?? -1);
var id = _presenter.GetPortfolioIdForContextMenu(selectedPortfolioName);
menu.SetHeaderTitle("Options".ToJ());
menu.Add(0, id, 1, "Rename".ToJ());
@ -67,15 +96,14 @@ namespace MonoStockPortfolio.Activites
if (item.TitleFormatted.ToS() == "Rename")
{
// Edit
var intent = EditPortfolioActivity.EditIntent(this, item.ItemId);
StartActivityForResult(intent, 0);
_presenter.EditPortfolio(item.ItemId);
return true;
}
if (item.TitleFormatted.ToS() == "Delete")
{
// Delete
_repo.DeletePortfolioById(item.ItemId);
RefreshList();
_presenter.DeletePortfolio(item.ItemId);
_presenter.RefreshPortfolioList();
return true;
}
return base.OnContextItemSelected(item);
@ -84,9 +112,9 @@ namespace MonoStockPortfolio.Activites
public override bool OnCreateOptionsMenu(IMenu menu)
{
var configItem = menu.Add(0, 1, 1, "Config".ToJ());
configItem.SetIcon(Android.Resource.Drawable.IcMenuPreferences);
configItem.SetIcon(Resource.Drawable.ic_menu_preferences);
var exitItem = menu.Add(0, 1, 1, "Exit".ToJ());
exitItem.SetIcon(Android.Resource.Drawable.IcMenuCloseClearCancel);
exitItem.SetIcon(Resource.Drawable.ic_menu_close_clear_cancel);
return true;
}
@ -95,13 +123,10 @@ namespace MonoStockPortfolio.Activites
switch (item.TitleFormatted.ToS())
{
case "Config":
var intent = ConfigActivity.GotoIntent(this);
// var intent = new Intent();
// intent.SetClassName(this, ConfigActivity.ClassName);
StartActivityForResult(intent, 0);
_presenter.GotoConfig();
return true;
case "Exit":
Finish();
_presenter.ExitApplication();
return true;
default:
return base.OnOptionsItemSelected(item);
@ -110,21 +135,19 @@ namespace MonoStockPortfolio.Activites
private void listView_ItemClick(object sender, ItemEventArgs e)
{
var intent = PortfolioActivity.ViewIntent(this, _portfolios[e.Position].ID ?? -1);
StartActivityForResult(intent, 0);
_presenter.ViewPortfolio(e.Position);
}
private void addPortfolioButton_Click(object sender, EventArgs e)
{
var intent = EditPortfolioActivity.AddIntent(this);
StartActivityForResult(intent, 0);
_presenter.AddNewPortfolio();
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
RefreshList();
_presenter.RefreshPortfolioList();
}
}
}

View file

@ -0,0 +1,79 @@
using System.Collections.Generic;
using Android.Runtime;
using MonoStockPortfolio.Core.PortfolioRepositories;
using System.Linq;
using MonoStockPortfolio.Entities;
namespace MonoStockPortfolio.Activites.MainScreen
{
public class MainPresenter : IMainPresenter
{
private IPortfolioRepository _repo;
private IMainView _currentView;
private IList<Portfolio> _portfolios;
private IList<Portfolio> Portfolios
{
get
{
return _portfolios ?? (_portfolios = _repo.GetAllPortfolios());
}
}
public MainPresenter(IPortfolioRepository portfolioRepository)
{
_repo = portfolioRepository;
}
public void Initialize(IMainView view)
{
_currentView = view;
RefreshPortfolioList();
}
public void RefreshPortfolioList()
{
_portfolios = null;
var portfolioNames = Portfolios.Select(p => p.Name).ToList();
_currentView.RefreshList(portfolioNames);
}
public void AddNewPortfolio()
{
_currentView.StartAddPortfolioActivity();
}
public void ViewPortfolio(int portfolioPosition)
{
_currentView.StartViewPortfolioActivity(Portfolios[portfolioPosition].ID ?? -1);
}
public void DeletePortfolio(int itemId)
{
_repo.DeletePortfolioById(itemId);
}
public void EditPortfolio(int itemId)
{
_currentView.StartEditPortfolioActivity(itemId);
}
public void GotoConfig()
{
_currentView.StartConfigActivity();
}
public void ExitApplication()
{
_currentView.ExitApplication();
}
public int GetPortfolioIdForContextMenu(string selectedPortfolioName)
{
var selectedPortfolio = _repo.GetPortfolioByName(selectedPortfolioName);
var id = (int)(selectedPortfolio.ID ?? -1);
return id;
}
}
}

View file

@ -1,233 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Views;
using Android.Widget;
using MonoStockPortfolio.Core;
using MonoStockPortfolio.Core.Config;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Core.Services;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Framework;
using Orientation = Android.Widget.Orientation;
namespace MonoStockPortfolio.Activites
{
[Activity(Label = "Portfolio", Name = "monostockportfolio.activites.PortfolioActivity")]
public class PortfolioActivity : Activity
{
[IoC]
private IPortfolioService _svc;
[IoC]
private IPortfolioRepository _repo;
[IoC]
private IConfigRepository _config;
[LazyView(Resource.Id.quoteListview)]
protected ListView QuoteListview;
[LazyView(Resource.Id.btnAddPosition)]
protected Button AddPositionButton;
[LazyView(Resource.Id.quoteHeaderLayout)]
protected LinearLayout QuoteListviewHeader;
private const string PORTFOLIOIDEXTRA = "monoStockPortfolio.PortfolioActivity.PortfolioID";
public static Intent ViewIntent(Context context, long portfolioId)
{
var intent = new Intent();
intent.SetClassName(context, ManifestNames.GetName<PortfolioActivity>());
intent.PutExtra(PORTFOLIOIDEXTRA, portfolioId);
return intent;
}
public long ThisPortofolioId { get { return Intent.GetLongExtra(PORTFOLIOIDEXTRA, -1); } }
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.portfolio);
Refresh();
WireUpEvents();
SetTitle();
}
public override bool OnCreateOptionsMenu(IMenu menu)
{
var item = menu.Add(0, 1, 1, "Refresh".ToJ());
item.SetIcon(Resource.Drawable.ic_menu_refresh);
return true;
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
switch (item.TitleFormatted.ToS())
{
case "Refresh": Refresh();
return true;
default:
return base.OnOptionsItemSelected(item);
}
}
public override void OnCreateContextMenu(IContextMenu menu, View v, IContextMenuContextMenuInfo menuInfo)
{
base.OnCreateContextMenu(menu, v, menuInfo);
var info = (AdapterView.AdapterContextMenuInfo)menuInfo;
var selectedPositionId = int.Parse(info.TargetView.Tag.ToString());
menu.SetHeaderTitle("Options".ToJ());
menu.Add(0, selectedPositionId, 1, "Edit".ToJ());
menu.Add(0, selectedPositionId, 2, "Delete".ToJ());
}
public override bool OnContextItemSelected(IMenuItem item)
{
if (item.TitleFormatted.ToS() == "Edit")
{
// Edit
var intent = EditPositionActivity.EditIntent(this, item.ItemId, ThisPortofolioId);
StartActivityForResult(intent, 0);
return true;
}
if (item.TitleFormatted.ToS() == "Delete")
{
// Delete
_repo.DeletePositionById(item.ItemId);
Refresh();
return true;
}
return base.OnContextItemSelected(item);
}
private void Refresh()
{
RefreshWorker();
UpdateHeader(GetStockItemsFromConfig());
}
[OnWorkerThread]
private void RefreshWorker()
{
var tickers = _svc.GetDetailedItems(ThisPortofolioId, GetStockItemsFromConfig());
if (tickers.Any())
{
RefreshUI(tickers);
}
else
{
ShowMessage("Please add positions!");
}
}
[OnGuiThread]
private void RefreshUI(IEnumerable<PositionResultsViewModel> tickers)
{
var listAdapter = new PositionArrayAdapter(this, tickers.ToArray());
QuoteListview.Adapter = listAdapter;
}
[OnGuiThread]
private void ShowMessage(string message)
{
var listAdapter = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleListItem1, new[] { message });
QuoteListview.Adapter = listAdapter;
}
[OnGuiThread]
private void UpdateHeader(IEnumerable<StockDataItem> items)
{
QuoteListviewHeader.RemoveAllViews();
var cellwidth = this.GetScreenWidth() / items.Count();
foreach (var stockDataItem in items)
{
var textItem = new TextView(this);
textItem.Text = stockDataItem.GetStringValue();
textItem.SetWidth(cellwidth);
textItem.SetTextColor(Resources.GetColor(Android.Resource.Color.Black));
QuoteListviewHeader.AddView(textItem);
}
QuoteListviewHeader.SetBackgroundResource(Android.Resource.Color.BackgroundLight);
}
public class PositionArrayAdapter : GenericArrayAdapter<PositionResultsViewModel>
{
public PositionArrayAdapter(Context context, IEnumerable<PositionResultsViewModel> results)
: base(context, results) { }
public override long GetItemId(int position)
{
return this[position].PositionId;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var item = this[position];
var width = Context.GetScreenWidth();
var columnWidth = width / item.Items.Count;
var row = new LinearLayout(Context);
row.Orientation = Orientation.Horizontal;
var portfolioActivity = (PortfolioActivity)Context;
foreach (var stockDataItem in portfolioActivity.GetStockItemsFromConfig())
{
var cell = new TextView(Context);
cell.Text = item.Items[stockDataItem];
cell.SetWidth(columnWidth);
RedGreenHighlighting(cell, item.Items);
row.Tag = item.PositionId;
row.AddView(cell);
}
return row;
}
private static void RedGreenHighlighting(TextView cell, IDictionary<StockDataItem, string> items)
{
if(items.ContainsKey(StockDataItem.GainLoss))
{
cell.SetTextColor(decimal.Parse(items[StockDataItem.GainLoss]) < 0 ? Color.Red : Color.Green);
}
}
}
private void WireUpEvents()
{
AddPositionButton.Click += addPositionButton_Click;
RegisterForContextMenu(QuoteListview);
}
private void SetTitle()
{
var portfolio = _repo.GetPortfolioById(ThisPortofolioId);
this.Title = "Portfolio: " + portfolio.Name;
}
void addPositionButton_Click(object sender, EventArgs e)
{
var intent = EditPositionActivity.AddIntent(this, ThisPortofolioId);
StartActivityForResult(intent, 0);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
Refresh();
}
public IEnumerable<StockDataItem> GetStockItemsFromConfig()
{
return _config.GetStockItems();
}
}
}

View file

@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace MonoStockPortfolio.Activites.PortfolioScreen
{
public interface IPortfolioPresenter
{
void Initialize(IPortfolioView view, long thisPortofolioId);
void AddNewPosition();
void MenuOptionSelected(string optionName);
IEnumerable<MenuOption> GetOptions();
IEnumerable<MenuOption> GetContextItems();
void ContextOptionSelected(string contextName, int positionId);
void RefreshPositions();
}
}

View file

@ -0,0 +1,17 @@
using System.Collections.Generic;
using MonoStockPortfolio.Entities;
namespace MonoStockPortfolio.Activites.PortfolioScreen
{
public interface IPortfolioView
{
void RefreshList(IEnumerable<PositionResultsViewModel> positions, IEnumerable<StockDataItem> getConfigItems);
void ShowMessage(string message);
void SetTitle(string title);
void StartAddNewPosition(long portfolioId);
void StartEditPosition(int positionId, long portfolioId);
void UpdateHeader(IEnumerable<StockDataItem> configItems);
void ShowProgressDialog(string loadingMessage);
void HideProgressDialog();
}
}

View file

@ -0,0 +1,10 @@
namespace MonoStockPortfolio.Activites.PortfolioScreen
{
public class MenuOption
{
public int Id { get; set; }
public int Order { get; set; }
public string Title { get; set; }
public int IconResource { get; set; }
}
}

View file

@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Views;
using Android.Widget;
using MonoStockPortfolio.Activites.EditPositionScreen;
using MonoStockPortfolio.Core;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Framework;
namespace MonoStockPortfolio.Activites.PortfolioScreen
{
[Activity(Label = "Portfolio", Name = "monostockportfolio.activites.mainscreen.PortfolioActivity")]
public class PortfolioActivity : Activity, IPortfolioView
{
[LazyView(Resource.Id.quoteListview)] protected ListView QuoteListview;
[LazyView(Resource.Id.btnAddPosition)] protected Button AddPositionButton;
[LazyView(Resource.Id.quoteHeaderLayout)] protected LinearLayout QuoteListviewHeader;
[IoC] private IPortfolioPresenter _presenter;
private const string PORTFOLIOIDEXTRA = "monoStockPortfolio.PortfolioActivity.PortfolioID";
public static Intent ViewIntent(Context context, long portfolioId)
{
var intent = new Intent();
intent.SetClassName(context, ManifestNames.GetName<PortfolioActivity>());
intent.PutExtra(PORTFOLIOIDEXTRA, portfolioId);
return intent;
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.portfolio);
_presenter.Initialize(this, Intent.GetLongExtra(PORTFOLIOIDEXTRA, -1));
WireUpEvents();
}
#region IPortfolioView members
[OnGuiThread]
public void RefreshList(IEnumerable<PositionResultsViewModel> positions, IEnumerable<StockDataItem> getConfigItems)
{
var listAdapter = new PositionArrayAdapter(this, positions.ToArray(), getConfigItems);
QuoteListview.Adapter = listAdapter;
}
[OnGuiThread]
public void ShowMessage(string message)
{
var listAdapter = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleListItem1, new[] { message });
QuoteListview.Adapter = listAdapter;
}
public void SetTitle(string title)
{
this.Title = title;
}
public void StartAddNewPosition(long portfolioId)
{
var intent = EditPositionActivity.AddIntent(this, portfolioId);
StartActivityForResult(intent, 0);
}
public void StartEditPosition(int positionId, long portfolioId)
{
var intent = EditPositionActivity.EditIntent(this, positionId, portfolioId);
StartActivityForResult(intent, 0);
}
public void UpdateHeader(IEnumerable<StockDataItem> configItems)
{
QuoteListviewHeader.RemoveAllViews();
var cellwidth = this.GetScreenWidth() / configItems.Count();
foreach (var stockDataItem in configItems)
{
var textItem = new TextView(this);
textItem.Text = stockDataItem.GetStringValue();
textItem.SetWidth(cellwidth);
textItem.SetTextColor(Resources.GetColor(Android.Resource.Color.Black));
QuoteListviewHeader.AddView(textItem);
}
QuoteListviewHeader.SetBackgroundResource(Android.Resource.Color.BackgroundLight);
}
private ProgressDialog _progressDialog;
[OnGuiThread]
public void ShowProgressDialog(string loadingMessage)
{
_progressDialog = new ProgressDialog(this);
_progressDialog.SetMessage(loadingMessage);
_progressDialog.SetProgressStyle(ProgressDialogStyle.Spinner);
_progressDialog.Show();
}
[OnGuiThread]
public void HideProgressDialog()
{
if (_progressDialog != null)
{
_progressDialog.Hide();
}
}
#endregion
private void WireUpEvents()
{
AddPositionButton.Click += AddPositionButton_Click;
RegisterForContextMenu(QuoteListview);
}
public override bool OnCreateOptionsMenu(IMenu menu)
{
foreach (var option in _presenter.GetOptions())
{
var item = menu.Add(0, option.Id, option.Order, option.Title.ToJ());
item.SetIcon(option.IconResource);
}
return base.OnCreateOptionsMenu(menu);
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
_presenter.MenuOptionSelected(item.TitleFormatted.ToS());
return base.OnOptionsItemSelected(item);
}
void AddPositionButton_Click(object sender, EventArgs e)
{
_presenter.AddNewPosition();
}
public override void OnCreateContextMenu(IContextMenu menu, View v, IContextMenuContextMenuInfo menuInfo)
{
base.OnCreateContextMenu(menu, v, menuInfo);
var info = (AdapterView.AdapterContextMenuInfo)menuInfo;
var selectedPositionId = int.Parse(info.TargetView.Tag.ToString());
menu.SetHeaderTitle("Options".ToJ());
foreach (var contextItem in _presenter.GetContextItems())
{
menu.Add(0, selectedPositionId, contextItem.Order, contextItem.Title.ToJ());
}
}
public override bool OnContextItemSelected(IMenuItem item)
{
_presenter.ContextOptionSelected(item.TitleFormatted.ToS(), item.ItemId);
return base.OnContextItemSelected(item);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
_presenter.RefreshPositions();
}
}
}

View file

@ -0,0 +1,124 @@
using System.Collections.Generic;
using System.Linq;
using MonoStockPortfolio.Core.Config;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Core.Services;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Framework;
namespace MonoStockPortfolio.Activites.PortfolioScreen
{
public class PortfolioPresenter : IPortfolioPresenter
{
private IPortfolioView _currentView;
private readonly IPortfolioRepository _portfolioRepo;
private readonly IPortfolioService _portfolioSvc;
private readonly IConfigRepository _configRepo;
private long _portfolioId;
private IEnumerable<PositionResultsViewModel> _positions;
private IEnumerable<StockDataItem> GetConfigItems()
{
return _configRepo.GetStockItems();
}
public PortfolioPresenter(IPortfolioRepository portfolioRepository, IPortfolioService portfolioService, IConfigRepository configRepository)
{
_portfolioRepo = portfolioRepository;
_configRepo = configRepository;
_portfolioSvc = portfolioService;
}
public void Initialize(IPortfolioView view, long thisPortofolioId)
{
_currentView = view;
_portfolioId = thisPortofolioId;
RefreshPositions();
UpdateHeader();
SetTitle();
}
private void UpdateHeader()
{
_currentView.UpdateHeader(GetConfigItems());
}
public void AddNewPosition()
{
_currentView.StartAddNewPosition(_portfolioId);
}
public void MenuOptionSelected(string optionName)
{
switch(optionName)
{
case "Refresh":
RefreshPositions();
break;
}
}
public IEnumerable<MenuOption> GetOptions()
{
var options = new List<MenuOption>();
options.Add(new MenuOption {Id = 1, Order = 1, Title = "Refresh", IconResource = Resource.Drawable.ic_menu_refresh});
return options;
}
public IEnumerable<MenuOption> GetContextItems()
{
var options = new List<MenuOption>();
options.Add(new MenuOption {Order = 1, Title = "Edit"});
options.Add(new MenuOption {Order = 2, Title = "Delete"});
return options;
}
public void ContextOptionSelected(string contextName, int positionId)
{
switch (contextName)
{
case "Edit":
_currentView.StartEditPosition(positionId, _portfolioId);
break;
case "Delete":
_portfolioRepo.DeletePositionById(positionId);
RefreshPositions();
break;
}
}
public void SetTitle()
{
var portfolio = _portfolioRepo.GetPortfolioById(_portfolioId);
var title = "Portfolio: " + portfolio.Name;
_currentView.SetTitle(title);
}
[OnWorkerThread]
public void RefreshPositions()
{
_currentView.ShowProgressDialog("Loading...Please wait...");
_positions = GetPositions();
if (_positions.Any())
{
_currentView.RefreshList(_positions, GetConfigItems());
}
else
{
_currentView.ShowMessage("Please add positions!");
}
_currentView.HideProgressDialog();
}
private IEnumerable<PositionResultsViewModel> GetPositions()
{
return _portfolioSvc.GetDetailedItems(_portfolioId, GetConfigItems());
}
}
}

View file

@ -0,0 +1,55 @@
using System.Collections.Generic;
using Android.Content;
using Android.Graphics;
using Android.Views;
using Android.Widget;
using MonoStockPortfolio.Entities;
using MonoStockPortfolio.Framework;
namespace MonoStockPortfolio.Activites.PortfolioScreen
{
public class PositionArrayAdapter : GenericArrayAdapter<PositionResultsViewModel>
{
private IEnumerable<StockDataItem> _configItems;
public PositionArrayAdapter(Context context, IEnumerable<PositionResultsViewModel> results, IEnumerable<StockDataItem> configItems)
: base(context, results)
{
_configItems = configItems;
}
public override long GetItemId(int position)
{
return this[position].PositionId;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var item = this[position];
var width = Context.GetScreenWidth();
var columnWidth = width / item.Items.Count;
var row = new LinearLayout(Context);
row.Orientation = Orientation.Horizontal;
foreach (var stockDataItem in _configItems)
{
var cell = new TextView(Context);
cell.Text = item.Items[stockDataItem];
cell.SetWidth(columnWidth);
RedGreenHighlighting(cell, item.Items);
row.Tag = item.PositionId;
row.AddView(cell);
}
return row;
}
private static void RedGreenHighlighting(TextView cell, IDictionary<StockDataItem, string> items)
{
if (items.ContainsKey(StockDataItem.GainLoss))
{
cell.SetTextColor(decimal.Parse(items[StockDataItem.GainLoss]) < 0 ? Color.Red : Color.Green);
}
}
}
}

View file

@ -1,4 +1,3 @@
using System.Reflection;
using Android.App;
using Android.Content;
using Android.Widget;

View file

@ -1,80 +1,73 @@
using System;
using System;
using System.Collections.Generic;
using Android.Widget;
using System.Linq;
namespace MonoStockPortfolio.Framework
{
public class FormValidator
{
private IList<KeyValuePair<EditText, Func<string>>> _dict;
private readonly IList<Func<string>> _list;
public FormValidator()
{
_dict = new List<KeyValuePair<EditText, Func<string>>>();
_list = new List<Func<string>>();
}
public void AddValidation(EditText control, Func<string> validationFunction)
public void AddValidation(Func<string> validationFunction)
{
_dict.Add(new KeyValuePair<EditText, Func<string>>(control, validationFunction));
}
public void AddRequired(EditText control, string errorMessage)
{
AddValidation(control, () => Required(control, errorMessage));
}
public void AddValidDecimal(EditText control, string errorMessage)
{
AddValidation(control, () => ValidDecimal(control, errorMessage));
}
public void AddValidPositiveDecimal(EditText control, string errorMessage)
{
AddValidation(control, () => ValidPositiveDecimal(control, errorMessage));
_list.Add(validationFunction);
}
public string Apply()
public void AddRequired(Func<string> getValue, string errorMessage)
{
var errorMessage = string.Empty;
foreach (var keyValuePair in _dict)
{
var result = keyValuePair.Value();
errorMessage += keyValuePair.Value();
if(result != string.Empty)
{
errorMessage += "\n";
}
}
return errorMessage;
AddValidation(() => Required(getValue(), errorMessage));
}
#region Validation Functions
private static string Required(EditText control, string errorMessage)
public void AddValidDecimal(Func<string> getValue, string errorMessage)
{
if (string.IsNullOrEmpty(control.Text.ToString()))
AddValidation(() => ValidDecimal(getValue(), errorMessage));
}
public void AddValidPositiveDecimal(Func<string> getValue, string errorMessage)
{
AddValidation(() => ValidPositiveDecimal(getValue(), errorMessage));
}
public IEnumerable<string> Apply()
{
return _list.Select(validation => validation())
.Where(result => !string.IsNullOrEmpty(result));
}
#region validation functions
private static string Required(string getValue, string errorMessage)
{
if (string.IsNullOrEmpty(getValue))
{
return errorMessage;
}
return string.Empty;
}
private static string ValidDecimal(EditText control, string errorMessage)
private static string ValidDecimal(string getValue, string errorMessage)
{
var test = control.Text.ToString();
decimal dummy;
if(!decimal.TryParse(test, out dummy))
if (!decimal.TryParse(getValue, out dummy))
{
return errorMessage;
}
return string.Empty;
}
private static string ValidPositiveDecimal(EditText control, string errorMessage)
private static string ValidPositiveDecimal(string getValue, string errorMessage)
{
if(ValidDecimal(control, errorMessage) == string.Empty)
if (ValidDecimal(getValue, errorMessage) == string.Empty)
{
var val = decimal.Parse(control.Text.ToString());
var val = decimal.Parse(getValue);
if (val >= 0) return string.Empty;
}
return errorMessage;
}
#endregion
}
}

View file

@ -1,3 +1,4 @@
using System;
using Android.Content;
using PostSharp.Aspects;
@ -6,21 +7,36 @@ namespace MonoStockPortfolio.Framework
public class IoCAttribute : LocationInterceptionAspect
{
public sealed override void OnGetValue(LocationInterceptionArgs args)
{
args.ProceedGetValue();
if (args.Value == null) // lazy loading
{
var context = args.Instance as Context;
if(context == null) throw new Exception("The IoC Aspect can only be used on a field within an Activity (or Context) object");
ResolveContextDependency((Context)args.Instance);
var dependencyType = args.Location.LocationType;
var instantiation = ServiceLocator.Get(dependencyType);
if (instantiation != null)
{
args.SetNewValue(instantiation);
}
args.ProceedGetValue();
}
}
private static void ResolveContextDependency(Context contextObject)
{
if (ServiceLocator.Context == null)
{
var activityContext = (Context)args.Instance;
ServiceLocator.Context = activityContext.ApplicationContext.ApplicationContext;
// note the double ApplicationContext
// is because the context's reference could get garbage collected while the app is still goin
// for whatever reason, but it's reference's reference is long-running
// and since this context dependency is mainly used for Sqlite, that's the most ideal one
ServiceLocator.Context = contextObject.ApplicationContext.ApplicationContext;
}
var locationType = args.Location.LocationType;
var instantiation = ServiceLocator.Get(locationType);
if (instantiation != null)
{
args.SetNewValue(instantiation);
}
args.ProceedGetValue();
}
}
}

View file

@ -11,7 +11,7 @@ namespace MonoStockPortfolio.Framework
public class InjectionConstructorAttribute : Attribute
{ }
private enum DependencyType
public enum DependencyType
{
None = 0, // Type is unset
Delegate, // A builder function
@ -20,7 +20,7 @@ namespace MonoStockPortfolio.Framework
Transient // Dynamically created transient object
}
private class DependencyInfo
public class DependencyInfo
{
public object Dependency { get; private set; }
public DependencyType DependencyType { get; private set; }
@ -32,8 +32,8 @@ namespace MonoStockPortfolio.Framework
}
}
private readonly static IDictionary<Type, DependencyInfo> dependencies = new Dictionary<Type, DependencyInfo>();
private readonly static IDictionary<Type, object> instances = new Dictionary<Type, object>();
public readonly static IDictionary<Type, DependencyInfo> dependencies = new Dictionary<Type, DependencyInfo>();
public readonly static IDictionary<Type, object> instances = new Dictionary<Type, object>();
public static void Register<TContract>(TContract instance)
{

View file

@ -1,32 +1,31 @@
using System;
using System.Threading;
using Android.App;
using PostSharp.Aspects;
namespace MonoStockPortfolio.Framework
{
public class OnWorkerThreadAttribute : MethodInterceptionAspect
{
public static IThreadingService ThreadingService;
public override void OnInvoke(MethodInterceptionArgs args)
{
var activity = args.Instance as Activity;
if(activity == null) throw new Exception("OnWorkerThread can only be used on methods in Activity classes");
var pd = ShowProgressDialog(activity);
pd.Show();
ThreadPool.QueueUserWorkItem(delegate
{
args.Proceed();
activity.RunOnUiThread(pd.Dismiss);
});
if(ThreadingService == null) ThreadingService = new ThreadingService();
ThreadingService.QueueUserWorkItem(d => args.Invoke(args.Arguments));
}
}
private static ProgressDialog ShowProgressDialog(Activity activity)
// this is kinda fail, but it helps with testing to inject in a "non threaded" service
// and I suppose it might make refactoring easier maybe...? just go with it
public interface IThreadingService
{
void QueueUserWorkItem(WaitCallback func);
}
public class ThreadingService : IThreadingService
{
public void QueueUserWorkItem(WaitCallback waitCallback)
{
var pd = new ProgressDialog(activity);
pd.SetMessage("Loading...Please wait...");
pd.SetProgressStyle(ProgressDialogStyle.Spinner);
return pd;
ThreadPool.QueueUserWorkItem(waitCallback);
}
}
}

View file

@ -1,5 +1,11 @@
using System;
using Android.Content;
using Android.Util;
using MonoStockPortfolio.Activites.ConfigScreen;
using MonoStockPortfolio.Activites.EditPortfolioScreen;
using MonoStockPortfolio.Activites.EditPositionScreen;
using MonoStockPortfolio.Activites.MainScreen;
using MonoStockPortfolio.Activites.PortfolioScreen;
using MonoStockPortfolio.Core.Config;
using MonoStockPortfolio.Core.PortfolioRepositories;
using MonoStockPortfolio.Core.Services;
@ -13,15 +19,32 @@ namespace MonoStockPortfolio.Framework
static ServiceLocator()
{
IttyBittyIoC.Register<IStockDataProvider>(() => new YahooStockDataProvider());
IttyBittyIoC.Register<IPortfolioService>(() => new PortfolioService(new AndroidSqlitePortfolioRepository(Context), new YahooStockDataProvider()));
IttyBittyIoC.Register<IPortfolioRepository>(() => new AndroidSqlitePortfolioRepository(Context));
IttyBittyIoC.Register<IConfigRepository>(() => new AndroidSqliteConfigRepository(Context));
// services/repositories
IttyBittyIoC.Register<Context>(() => Context);
IttyBittyIoC.Register<IStockDataProvider, YahooStockDataProvider>();
IttyBittyIoC.Register<IPortfolioRepository,AndroidSqlitePortfolioRepository>();
IttyBittyIoC.Register<IPortfolioService, PortfolioService>();
IttyBittyIoC.Register<IConfigRepository, AndroidSqliteConfigRepository>();
// presenters
IttyBittyIoC.Register<IMainPresenter, MainPresenter>();
IttyBittyIoC.Register<IPortfolioPresenter, PortfolioPresenter>();
IttyBittyIoC.Register<IEditPortfolioPresenter, EditPortfolioPresenter>();
IttyBittyIoC.Register<IEditPositionPresenter, EditPositionPresenter>();
IttyBittyIoC.Register<IConfigPresenter, ConfigPresenter>();
}
public static object Get(Type serviceType)
{
return IttyBittyIoC.Resolve(serviceType);
try
{
return IttyBittyIoC.Resolve(serviceType);
}
catch (Exception)
{
Log.Error("ServiceLocatorGet", "Unable to resolve type: " + serviceType.Name);
throw;
}
}
}
}

View file

@ -17,6 +17,8 @@
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidSupportedAbis>armeabi</AndroidSupportedAbis>
<AndroidApplication>true</AndroidApplication>
<AndroidStoreUncompressedFileExtensions />
<TargetFrameworkVersion>v2.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -26,6 +28,8 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -34,6 +38,8 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
@ -48,14 +54,33 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Activites\ConfigActivity.cs" />
<Compile Include="Activites\EditPortfolioActivity.cs" />
<Compile Include="Activites\ConfigScreen\ConfigActivity.cs" />
<Compile Include="Activites\ConfigScreen\ConfigPresenter.cs" />
<Compile Include="Activites\ConfigScreen\IConfigPresenter.cs" />
<Compile Include="Activites\ConfigScreen\IConfigView.cs" />
<Compile Include="Activites\EditPortfolioScreen\EditPortfolioActivity.cs" />
<Compile Include="Activites\EditPortfolioScreen\EditPortfolioPresenter.cs" />
<Compile Include="Activites\EditPortfolioScreen\IEditPortfolioPresenter.cs" />
<Compile Include="Activites\EditPortfolioScreen\IEditPortfolioView.cs" />
<Compile Include="Activites\EditPositionScreen\EditPositionPresenter.cs" />
<Compile Include="Activites\EditPositionScreen\IEditPositionPresenter.cs" />
<Compile Include="Activites\EditPositionScreen\IEditPositionView.cs" />
<Compile Include="Activites\EditPositionScreen\PositionInputModel.cs" />
<Compile Include="Framework\FormValidator.cs" />
<Compile Include="Activites\MainScreen\IMainPresenter.cs" />
<Compile Include="Activites\MainScreen\IMainView.cs" />
<Compile Include="Activites\MainScreen\MainPresenter.cs" />
<Compile Include="Activites\PortfolioScreen\MenuOption.cs" />
<Compile Include="Activites\PortfolioScreen\IPortfolioPresenter.cs" />
<Compile Include="Activites\PortfolioScreen\IPortfolioView.cs" />
<Compile Include="Activites\PortfolioScreen\PortfolioPresenter.cs" />
<Compile Include="Activites\PortfolioScreen\PositionArrayAdapter.cs" />
<Compile Include="Framework\ActivityExtensions.cs" />
<Compile Include="Framework\ContextExtensions.cs" />
<Compile Include="Framework\GenericArrayAdapter.cs" />
<Compile Include="Activites\PortfolioActivity.cs" />
<Compile Include="Activites\MainActivity.cs" />
<Compile Include="Activites\EditPositionActivity.cs" />
<Compile Include="Activites\PortfolioScreen\PortfolioActivity.cs" />
<Compile Include="Activites\MainScreen\MainActivity.cs" />
<Compile Include="Activites\EditPositionScreen\EditPositionActivity.cs" />
<Compile Include="Framework\IoCAttribute.cs" />
<Compile Include="Framework\IttyBittyIoC.cs" />
<Compile Include="Framework\LazyViewAttribute.cs" />
@ -66,7 +91,6 @@
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Framework\StringExtensions.cs" />
<Compile Include="Framework\FormValidator.cs" />
</ItemGroup>
<ItemGroup>
<None Include="PostSharp.Custom.targets" />
@ -75,9 +99,6 @@
<SubType>AndroidResource</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<None Include="Properties\AndroidManifest.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\main.xml">
<SubType>Designer</SubType>
@ -111,7 +132,15 @@
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Content Include="Properties\AndroidManifest.xml" />
<AndroidResource Include="Resources\drawable-hdpi\ic_menu_preferences.png" />
<AndroidResource Include="Resources\drawable-ldpi\ic_menu_preferences.png" />
<AndroidResource Include="Resources\drawable-mdpi\ic_menu_preferences.png" />
<AndroidResource Include="Resources\drawable-hdpi\ic_menu_close_clear_cancel.png" />
<AndroidResource Include="Resources\drawable-ldpi\ic_menu_close_clear_cancel.png" />
<AndroidResource Include="Resources\drawable-mdpi\ic_menu_close_clear_cancel.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\ic_menu_refresh.png" />
</ItemGroup>

View file

@ -2,5 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.monostockportfolio" android:versionCode="1" android:versionName="1.0">
<application android:label="MonoStockPortfolio">
</application>
<uses-sdk android:minSdkVersion="8" />
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View file

@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:2.0.50727.4952
// Runtime Version:2.0.50727.5420
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@ -27,10 +27,16 @@ namespace MonoStockPortfolio
{
// aapt resource value: 0x7f020000
public const int ic_menu_refresh = 2130837504;
public const int ic_menu_close_clear_cancel = 2130837504;
// aapt resource value: 0x7f020001
public const int icon = 2130837505;
public const int ic_menu_preferences = 2130837505;
// aapt resource value: 0x7f020002
public const int ic_menu_refresh = 2130837506;
// aapt resource value: 0x7f020003
public const int icon = 2130837507;
private Drawable()
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

7
README
View file

@ -1,6 +1,12 @@
Monodroid Stock Portfolio
=========================
Updates
-------
3/15/2011 - I'm no longer using FileHelpers, since I can't get it to work with MonoDroid anymore (even with compiling it myself)
so I switched to LumenWorks.Framework.IO.Csv because writing CSV parsing is a hard problem that I don't need to solve again
What...? Why...?
----------------
@ -23,3 +29,4 @@ domain anyway.
To get stock quotes, I'm using a Yahoo API coupled with the FileHelpers library
(since the Yahoo API outputs a CSV)

View file

@ -1,31 +0,0 @@
Tasks:
- add a portfolio
- delete a portfolio
- select a portfolio
- refresh it to get updates on positions
- add new positions
- edit positions
- delete positions
main view
---------
add portfolio button
clickable list of portfolios
click portfolio to go to single portfolio view
long hold portfolio to get options:
-delete
single portfolio view
-----------------
add ticker button
refresh button
list of:
ticker, price, gain/loss, time
long hold ticker to get options:
-edit
-delete
there should be a config somewhere:
-----------------------------------
to configure which columns are shown in the single portfolio view

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,2 @@
Please checkout the online documentation at:
http://www.telerik.com/help/justmock/introduction.html

Binary file not shown.

View file

@ -0,0 +1,5 @@
<TestRunner>
<FriendlyName>Machine.Specifications 0.4.8-590b2b1</FriendlyName>
<AssemblyPath>Machine.Specifications.TDNetRunner.dll</AssemblyPath>
<TypeName>Machine.Specifications.TDNetRunner.SpecificationRunner</TypeName>
</TestRunner>

Binary file not shown.

View file

@ -0,0 +1,347 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>CommandLine</name>
</assembly>
<members>
<member name="T:CommandLine.Text.HelpText">
<summary>
Models an help text and collects related informations.
You can assign it in place of a <see cref="T:System.String"/> instance, this is why
this type lacks a method to add lines after the options usage informations;
simple use a <see cref="T:System.Text.StringBuilder"/> or similar solutions.
</summary>
</member>
<member name="M:CommandLine.Text.HelpText.#ctor(System.String)">
<summary>
Initializes a new instance of the <see cref="T:CommandLine.Text.HelpText"/> class
specifying heading informations.
</summary>
<param name="heading">A string with heading information or
an instance of <see cref="T:CommandLine.Text.HeadingInfo"/>.</param>
<exception cref="T:System.ArgumentException">Thrown when parameter <paramref name="heading"/> is null or empty string.</exception>
</member>
<member name="M:CommandLine.Text.HelpText.AddPreOptionsLine(System.String)">
<summary>
Adds a text line after copyright and before options usage informations.
</summary>
<param name="value">A <see cref="T:System.String"/> instance.</param>
<exception cref="T:System.ArgumentNullException">Thrown when parameter <paramref name="value"/> is null or empty string.</exception>
</member>
<member name="M:CommandLine.Text.HelpText.AddOptions(System.Object)">
<summary>
Adds a text block with options usage informations.
</summary>
<param name="options">The instance that collected command line arguments parsed with <see cref="T:CommandLine.Parser"/> class.</param>
<exception cref="T:System.ArgumentNullException">Thrown when parameter <paramref name="options"/> is null.</exception>
</member>
<member name="M:CommandLine.Text.HelpText.AddOptions(System.Object,System.String)">
<summary>
Adds a text block with options usage informations.
</summary>
<param name="options">The instance that collected command line arguments parsed with the <see cref="T:CommandLine.Parser"/> class.</param>
<param name="requiredWord">The word to use when the option is required.</param>
<exception cref="T:System.ArgumentNullException">Thrown when parameter <paramref name="options"/> is null.</exception>
<exception cref="T:System.ArgumentNullException">Thrown when parameter <paramref name="requiredWord"/> is null or empty string.</exception>
</member>
<member name="M:CommandLine.Text.HelpText.ToString">
<summary>
Returns the help informations as a <see cref="T:System.String"/>.
</summary>
<returns>The <see cref="T:System.String"/> that contains the help informations.</returns>
</member>
<member name="M:CommandLine.Text.HelpText.op_Implicit(CommandLine.Text.HelpText)~System.String">
<summary>
Converts the help informations to a <see cref="T:System.String"/>.
</summary>
<param name="info">This <see cref="T:CommandLine.Text.HelpText"/> instance.</param>
<returns>The <see cref="T:System.String"/> that contains the help informations.</returns>
</member>
<member name="P:CommandLine.Text.HelpText.Copyright">
<summary>
Sets the copyright information string.
You can directly assign a <see cref="T:CommandLine.Text.CopyrightInfo"/> instance.
</summary>
</member>
<member name="T:CommandLine.OptionAttribute">
<summary>
Models an option specification.
</summary>
</member>
<member name="T:CommandLine.BaseOptionAttribute">
<summary>
Provides base properties for creating an attribute, used to define rules for command line parsing.
</summary>
</member>
<member name="P:CommandLine.BaseOptionAttribute.ShortName">
<summary>
Short name of this command line option. This name is usually a single character.
</summary>
</member>
<member name="P:CommandLine.BaseOptionAttribute.LongName">
<summary>
Long name of this command line option. This name is usually a single english word.
</summary>
</member>
<member name="P:CommandLine.BaseOptionAttribute.Required">
<summary>
True if this command line option is required.
</summary>
</member>
<member name="P:CommandLine.BaseOptionAttribute.HelpText">
<summary>
A short description of this command line option. Usually a sentence summary.
</summary>
</member>
<member name="M:CommandLine.OptionAttribute.#ctor(System.String,System.String)">
<summary>
Initializes a new instance of the <see cref="T:CommandLine.OptionAttribute"/> class.
</summary>
<param name="shortName">The short name of the option or null if not used.</param>
<param name="longName">The long name of the option or null if not used.</param>
</member>
<member name="T:CommandLine.Text.CopyrightInfo">
<summary>
Models the copyright informations part of an help text.
You can assign it where you assign any <see cref="T:System.String"/> instance.
</summary>
</member>
<member name="M:CommandLine.Text.CopyrightInfo.#ctor">
<summary>
Initializes a new instance of the <see cref="T:CommandLine.Text.CopyrightInfo"/> class.
</summary>
</member>
<member name="M:CommandLine.Text.CopyrightInfo.#ctor(System.String,System.Int32)">
<summary>
Initializes a new instance of the <see cref="T:CommandLine.Text.CopyrightInfo"/> class
specifying author and year.
</summary>
<param name="author">The company or person holding the copyright.</param>
<param name="year">The year of coverage of copyright.</param>
<exception cref="T:System.ArgumentException">Thrown when parameter <paramref name="author"/> is null or empty string.</exception>
</member>
<member name="M:CommandLine.Text.CopyrightInfo.#ctor(System.String,System.Int32[])">
<summary>
Initializes a new instance of the <see cref="T:CommandLine.Text.CopyrightInfo"/> class
specifying author and years.
</summary>
<param name="author">The company or person holding the copyright.</param>
<param name="years">The years of coverage of copyright.</param>
<exception cref="T:System.ArgumentException">Thrown when parameter <paramref name="author"/> is null or empty string.</exception>
<exception cref="T:System.ArgumentOutOfRangeException">Thrown when parameter <paramref name="years"/> is not supplied.</exception>
</member>
<member name="M:CommandLine.Text.CopyrightInfo.#ctor(System.Boolean,System.String,System.Int32[])">
<summary>
Initializes a new instance of the <see cref="T:CommandLine.Text.CopyrightInfo"/> class
specifying symbol case, author and years.
</summary>
<param name="isSymbolUpper">The case of the copyright symbol.</param>
<param name="author">The company or person holding the copyright.</param>
<param name="years">The years of coverage of copyright.</param>
<exception cref="T:System.ArgumentException">Thrown when parameter <paramref name="author"/> is null or empty string.</exception>
<exception cref="T:System.ArgumentOutOfRangeException">Thrown when parameter <paramref name="years"/> is not supplied.</exception>
</member>
<member name="M:CommandLine.Text.CopyrightInfo.ToString">
<summary>
Returns the copyright informations as a <see cref="T:System.String"/>.
</summary>
<returns>The <see cref="T:System.String"/> that contains the copyright informations.</returns>
</member>
<member name="M:CommandLine.Text.CopyrightInfo.op_Implicit(CommandLine.Text.CopyrightInfo)~System.String">
<summary>
Converts the copyright informations to a <see cref="T:System.String"/>.
</summary>
<param name="info">This <see cref="T:CommandLine.Text.CopyrightInfo"/> instance.</param>
<returns>The <see cref="T:System.String"/> that contains the copyright informations.</returns>
</member>
<member name="M:CommandLine.Text.CopyrightInfo.FormatYears(System.Int32[])">
<summary>
When overridden in a derived class, allows to specify a new algorithm to render copyright years
as a <see cref="T:System.String"/> instance.
</summary>
<param name="years">A <see cref="T:System.Int32"/> array of years.</param>
<returns>A <see cref="T:System.String"/> instance with copyright years.</returns>
</member>
<member name="P:CommandLine.Text.CopyrightInfo.CopyrightWord">
<summary>
When overridden in a derived class, allows to specify a different copyright word.
</summary>
</member>
<member name="T:CommandLine.HelpOptionAttribute">
<summary>
Indicates the instance method that must be invoked when it becomes necessary show your help screen.
The method signature is an instance method with no parameters and <see cref="T:System.String"/>
return value.
</summary>
</member>
<member name="M:CommandLine.HelpOptionAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:CommandLine.HelpOptionAttribute"/> class.
</summary>
</member>
<member name="M:CommandLine.HelpOptionAttribute.#ctor(System.String,System.String)">
<summary>
Initializes a new instance of the <see cref="T:CommandLine.HelpOptionAttribute"/> class.
Allows you to define short and long option names.
</summary>
<param name="shortName">The short name of the option or null if not used.</param>
<param name="longName">The long name of the option or null if not used.</param>
</member>
<member name="P:CommandLine.HelpOptionAttribute.Required">
<summary>
Returns always false for this kind of option.
This behaviour can't be changed by design; if you try set <see cref="P:CommandLine.HelpOptionAttribute.Required"/>
an <see cref="T:System.InvalidOperationException"/> will be thrown.
</summary>
</member>
<member name="T:CommandLine.OptionListAttribute">
<summary>
Models an option that can accept multiple values.
Must be applied to a field compatible with an <see cref="T:System.Collections.Generic.IList`1"/> interface
of <see cref="T:System.String"/> instances.
</summary>
</member>
<member name="M:CommandLine.OptionListAttribute.#ctor(System.String,System.String)">
<summary>
Initializes a new instance of the <see cref="T:CommandLine.OptionListAttribute"/> class.
</summary>
<param name="shortName">The short name of the option or null if not used.</param>
<param name="longName">The long name of the option or null if not used.</param>
</member>
<member name="M:CommandLine.OptionListAttribute.#ctor(System.String,System.String,System.Char)">
<summary>
Initializes a new instance of the <see cref="T:CommandLine.OptionListAttribute"/> class.
</summary>
<param name="shortName">The short name of the option or null if not used.</param>
<param name="longName">The long name of the option or null if not used.</param>
<param name="separator">Values separator character.</param>
</member>
<member name="P:CommandLine.OptionListAttribute.Separator">
<summary>
Gets or sets the values separator character.
</summary>
</member>
<member name="T:CommandLine.ValueListAttribute">
<summary>
Models a list of command line arguments that are not options.
Must be applied to a field compatible with an <see cref="T:System.Collections.Generic.IList`1"/> interface
of <see cref="T:System.String"/> instances.
</summary>
</member>
<member name="M:CommandLine.ValueListAttribute.#ctor(System.Type)">
<summary>
Initializes a new instance of the <see cref="T:CommandLine.ValueListAttribute"/> class.
</summary>
<param name="concreteType">A type that implements <see cref="T:System.Collections.Generic.IList`1"/>.</param>
<exception cref="T:System.ArgumentNullException">Thrown if <paramref name="concreteType"/> is null.</exception>
</member>
<member name="P:CommandLine.ValueListAttribute.MaximumElements">
<summary>
Gets or sets the maximum element allow for the list managed by <see cref="T:CommandLine.ValueListAttribute"/> type.
If lesser than 0, no upper bound is fixed.
If equal to 0, no elements are allowed.
</summary>
</member>
<member name="T:CommandLine.Parser">
<summary>
Provides methods to parse command line arguments. This class cannot be inherited.
</summary>
</member>
<member name="M:CommandLine.Parser.ParseArguments(System.String[],System.Object)">
<summary>
Parses a <see cref="T:System.String"/> array of command line arguments,
setting values read in <paramref name="options"/> parameter instance.
</summary>
<param name="args">A <see cref="T:System.String"/> array of command line arguments.</param>
<param name="options">An instance to receive values.
Parsing rules are defined using <see cref="T:CommandLine.BaseOptionAttribute"/> derived types.</param>
<returns>True if parsing process succeed.</returns>
<exception cref="T:System.ArgumentNullException">Thrown if <paramref name="args"/> is null.</exception>
<exception cref="T:System.ArgumentNullException">Thrown if <paramref name="options"/> is null.</exception>
</member>
<member name="M:CommandLine.Parser.ParseArguments(System.String[],System.Object,System.IO.TextWriter)">
<summary>
Parses a <see cref="T:System.String"/> array of command line arguments,
setting values read in <paramref name="options"/> parameter instance.
This overloads allows you to specify a <see cref="T:System.IO.TextWriter"/>
derived instance for write text messages.
</summary>
<param name="args">A <see cref="T:System.String"/> array of command line arguments.</param>
<param name="options">An instance to receive values.
Parsing rules are defined using <see cref="T:CommandLine.BaseOptionAttribute"/> derived types.</param>
<param name="helpWriter">Any instance derived from <see cref="T:System.IO.TextWriter"/>,
usually <see cref="P:System.Console.Out"/>.</param>
<returns>True if parsing process succeed.</returns>
<exception cref="T:System.ArgumentNullException">Thrown if <paramref name="args"/> is null.</exception>
<exception cref="T:System.ArgumentNullException">Thrown if <paramref name="options"/> is null.</exception>
<exception cref="T:System.ArgumentNullException">Thrown if <paramref name="helpWriter"/> is null.</exception>
</member>
<member name="T:CommandLine.IncompatibleTypesException">
<summary>
Represents the exception that is thrown when an attempt to assign incopatible types.
</summary>
</member>
<member name="T:CommandLine.Text.HeadingInfo">
<summary>
Models the heading informations part of an help text.
You can assign it where you assign any <see cref="T:System.String"/> instance.
</summary>
</member>
<member name="M:CommandLine.Text.HeadingInfo.#ctor(System.String)">
<summary>
Initializes a new instance of the <see cref="T:CommandLine.Text.HeadingInfo"/> class
specifying program name.
</summary>
<param name="programName">The name of the program.</param>
<exception cref="T:System.ArgumentException">Thrown when parameter <paramref name="programName"/> is null or empty string.</exception>
</member>
<member name="M:CommandLine.Text.HeadingInfo.#ctor(System.String,System.String)">
<summary>
Initializes a new instance of the <see cref="T:CommandLine.Text.HeadingInfo"/> class
specifying program name and version.
</summary>
<param name="programName">The name of the program.</param>
<param name="version">The version of the program.</param>
<exception cref="T:System.ArgumentException">Thrown when parameter <paramref name="programName"/> is null or empty string.</exception>
</member>
<member name="M:CommandLine.Text.HeadingInfo.ToString">
<summary>
Returns the heading informations as a <see cref="T:System.String"/>.
</summary>
<returns>The <see cref="T:System.String"/> that contains the heading informations.</returns>
</member>
<member name="M:CommandLine.Text.HeadingInfo.op_Implicit(CommandLine.Text.HeadingInfo)~System.String">
<summary>
Converts the heading informations to a <see cref="T:System.String"/>.
</summary>
<param name="info">This <see cref="T:CommandLine.Text.HeadingInfo"/> instance.</param>
<returns>The <see cref="T:System.String"/> that contains the heading informations.</returns>
</member>
<member name="M:CommandLine.Text.HeadingInfo.WriteMessage(System.String,System.IO.TextWriter)">
<summary>
Writes out a string and a new line using the program name specified in the constructor
and <paramref name="message"/> parameter.
</summary>
<param name="message">The <see cref="T:System.String"/> message to write.</param>
<param name="writer">The target <see cref="T:System.IO.TextWriter"/> derived type.</param>
<exception cref="T:System.ArgumentException">Thrown when parameter <paramref name="message"/> is null or empty string.</exception>
<exception cref="T:System.ArgumentNullException">Thrown when parameter <paramref name="writer"/> is null.</exception>
</member>
<member name="M:CommandLine.Text.HeadingInfo.WriteMessage(System.String)">
<summary>
Writes out a string and a new line using the program name specified in the constructor
and <paramref name="message"/> parameter to standard output stream.
</summary>
<param name="message">The <see cref="T:System.String"/> message to write.</param>
<exception cref="T:System.ArgumentException">Thrown when parameter <paramref name="message"/> is null or empty string.</exception>
</member>
<member name="M:CommandLine.Text.HeadingInfo.WriteError(System.String)">
<summary>
Writes out a string and a new line using the program name specified in the constructor
and <paramref name="message"/> parameter to standard error stream.
</summary>
<param name="message">The <see cref="T:System.String"/> message to write.</param>
<exception cref="T:System.ArgumentException">Thrown when parameter <paramref name="message"/> is null or empty string.</exception>
</member>
</members>
</doc>

View file

@ -0,0 +1,6 @@
mkdir "%APPDATA%\JetBrains\ReSharper\v4.1\vs9.0\Plugins"
copy Machine.Specifications.dll "%APPDATA%\JetBrains\ReSharper\v4.1\vs9.0\Plugins"
copy Machine.Specifications.pdb "%APPDATA%\JetBrains\ReSharper\v4.1\vs9.0\Plugins"
copy Machine.Specifications.ReSharperRunner.4.1.dll "%APPDATA%\JetBrains\ReSharper\v4.1\vs9.0\Plugins"
copy Machine.Specifications.ReSharperRunner.4.1.pdb "%APPDATA%\JetBrains\ReSharper\v4.1\vs9.0\Plugins"

View file

@ -0,0 +1,6 @@
mkdir "%APPDATA%\JetBrains\ReSharper\v4.5\vs9.0\Plugins"
copy Machine.Specifications.dll "%APPDATA%\JetBrains\ReSharper\v4.5\vs9.0\Plugins"
copy Machine.Specifications.pdb "%APPDATA%\JetBrains\ReSharper\v4.5\vs9.0\Plugins"
copy Machine.Specifications.ReSharperRunner.4.5.dll "%APPDATA%\JetBrains\ReSharper\v4.5\vs9.0\Plugins"
copy Machine.Specifications.ReSharperRunner.4.5.pdb "%APPDATA%\JetBrains\ReSharper\v4.5\vs9.0\Plugins"

View file

@ -0,0 +1,6 @@
mkdir "%APPDATA%\JetBrains\ReSharper\v5.0\vs9.0\Plugins"
copy Machine.Specifications.dll "%APPDATA%\JetBrains\ReSharper\v5.0\vs9.0\Plugins"
copy Machine.Specifications.pdb "%APPDATA%\JetBrains\ReSharper\v5.0\vs9.0\Plugins"
copy Machine.Specifications.ReSharperRunner.5.0.dll "%APPDATA%\JetBrains\ReSharper\v5.0\vs9.0\Plugins"
copy Machine.Specifications.ReSharperRunner.5.0.pdb "%APPDATA%\JetBrains\ReSharper\v5.0\vs9.0\Plugins"

View file

@ -0,0 +1,6 @@
mkdir "%APPDATA%\JetBrains\ReSharper\v5.0\vs10.0\Plugins"
copy Machine.Specifications.dll "%APPDATA%\JetBrains\ReSharper\v5.0\vs10.0\Plugins"
copy Machine.Specifications.pdb "%APPDATA%\JetBrains\ReSharper\v5.0\vs10.0\Plugins"
copy Machine.Specifications.ReSharperRunner.5.0.dll "%APPDATA%\JetBrains\ReSharper\v5.0\vs10.0\Plugins"
copy Machine.Specifications.ReSharperRunner.5.0.pdb "%APPDATA%\JetBrains\ReSharper\v5.0\vs10.0\Plugins"

View file

@ -0,0 +1,6 @@
mkdir "%APPDATA%\JetBrains\ReSharper\v5.1\vs9.0\Plugins"
copy Machine.Specifications.dll "%APPDATA%\JetBrains\ReSharper\v5.1\vs9.0\Plugins"
copy Machine.Specifications.pdb "%APPDATA%\JetBrains\ReSharper\v5.1\vs9.0\Plugins"
copy Machine.Specifications.ReSharperRunner.5.1.dll "%APPDATA%\JetBrains\ReSharper\v5.1\vs9.0\Plugins"
copy Machine.Specifications.ReSharperRunner.5.1.pdb "%APPDATA%\JetBrains\ReSharper\v5.1\vs9.0\Plugins"

View file

@ -0,0 +1,6 @@
mkdir "%APPDATA%\JetBrains\ReSharper\v5.1\vs10.0\Plugins"
copy Machine.Specifications.dll "%APPDATA%\JetBrains\ReSharper\v5.1\vs10.0\Plugins"
copy Machine.Specifications.pdb "%APPDATA%\JetBrains\ReSharper\v5.1\vs10.0\Plugins"
copy Machine.Specifications.ReSharperRunner.5.1.dll "%APPDATA%\JetBrains\ReSharper\v5.1\vs10.0\Plugins"
copy Machine.Specifications.ReSharperRunner.5.1.pdb "%APPDATA%\JetBrains\ReSharper\v5.1\vs10.0\Plugins"

View file

@ -0,0 +1,16 @@
@echo off & if not "%ECHO%"=="" echo %ECHO%
setlocal
set LOCALDIR=%~dp0
echo Windows Registry Editor Version 5.00 > MSpecTDNet.reg
echo [HKEY_CURRENT_USER\Software\MutantDesign\TestDriven.NET\TestRunners\MSpec] >> MSpecTDNet.reg
echo "Application"="" >> MSpecTDNet.reg
echo "AssemblyPath"="%LOCALDIR:\=\\%Machine.Specifications.TDNetRunner.dll" >> MSpecTDNet.reg
echo "TargetFrameworkAssemblyName"="Machine.Specifications" >> MSpecTDNet.reg
echo "TypeName"="Machine.Specifications.TDNetRunner.SpecificationRunner" >> MSpecTDNet.reg
echo @="5" >> MSpecTDNet.reg
regedit MSpecTDNet.reg
del MSpecTDNet.reg

View file

@ -0,0 +1,16 @@
@echo off & if not "%ECHO%"=="" echo %ECHO%
setlocal
set LOCALDIR=%~dp0
echo Windows Registry Editor Version 5.00 > MSpecTDNet.reg
echo [HKEY_CURRENT_USER\Software\MutantDesign\TestDriven.NET\TestRunners\MSpec] >> MSpecTDNet.reg
echo "Application"="" >> MSpecTDNet.reg
echo "AssemblyPath"="%LOCALDIR:\=\\%Machine.Specifications.TDNetRunner.dll" >> MSpecTDNet.reg
echo "TargetFrameworkAssemblyName"="Machine.Specifications" >> MSpecTDNet.reg
echo "TypeName"="Machine.Specifications.TDNetRunner.SpecificationRunner" >> MSpecTDNet.reg
echo @="5" >> MSpecTDNet.reg
regedit /s MSpecTDNet.reg
del MSpecTDNet.reg

View file

@ -0,0 +1,54 @@
Copyright (c) 2008 Machine Project
Portions Copyright (c) 2008 Jacob Lewallen, Aaron Jensen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*****************************
Some parts licensed under MS-PL
*****************************
This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
1. Definitions
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
A "contribution" is the original software, or any additions or changes to the software.
A "contributor" is any person that distributes its contribution under this license.
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
2. Grant of Rights
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
3. Conditions and Limitations
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8" ?>
<plugin pluginId="Gallio.MSpecAdapter"
recommendedInstallationPath="MSpec"
xmlns="http://www.gallio.org/">
<traits>
<name>Machine Specifications Adapter Plugin</name>
<version>0.4.8.0</version>
<description>Provides support for running MSpec within Gallio. Requires the assemblies from MSpec.</description>
</traits>
<dependencies>
<dependency pluginId="Gallio" />
</dependencies>
<files>
<file path="Machine.Specifications.dll" />
<file path="Machine.Specifications.GallioAdapter.plugin" />
<file path="Machine.Specifications.GallioAdapter.3.1.dll" />
<!--<file path="Readme.txt" />-->
</files>
<assemblies>
<assembly fullName="Machine.Specifications.GallioAdapter.3.1, Version=0.4.8.0, Culture=neutral, PublicKeyToken=null"
codeBase="Machine.Specifications.GallioAdapter.3.1.dll"
qualifyPartialName="true" />
<assembly fullName="Machine.Specifications, Version=0.4.8.0, Culture=neutral, PublicKeyToken=5c474de7a495cff1"
codeBase="Machine.Specifications.dll" />
</assemblies>
<components>
<component componentId="Machine.Specifications"
serviceId="Gallio.TestFramework"
componentType="Machine.Specifications.GallioAdapter.MachineSpecificationsFramework, Machine.Specifications.GallioAdapter.3.1">
<traits>
<name>Machine Specifications</name>
<frameworkAssemblies>Machine.Specifications, Version=0.4.8.0</frameworkAssemblies>
<version>0.4.8.0</version>
<fileTypes>Assembly</fileTypes>
</traits>
</component>
</components>
</plugin>

Some files were not shown because too many files have changed in this diff Show more