Browse Source

Merge branch 'dev' of https://github.com/volosoft/abp into dev

pull/3164/head
Yunus Emre Kalkan 6 years ago
parent
commit
95d400edbd
  1. 52
      docs/en/Application-Services.md
  2. 38
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
  3. 6
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs
  4. 341
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs
  5. 262
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs
  6. 6
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityNotFoundException.cs
  7. 30
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictAppService.cs
  8. 11
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictKey.cs
  9. 14
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/DistrictDto.cs
  10. 25
      templates/app/react-native/.eslintrc.json
  11. 14
      templates/app/react-native/.gitignore
  12. 8
      templates/app/react-native/.prettierrc
  13. 3
      templates/app/react-native/.vscode/extensions.json
  14. 25
      templates/app/react-native/App.js
  15. 31
      templates/app/react-native/Environment.js
  16. 30
      templates/app/react-native/app.json
  17. BIN
      templates/app/react-native/assets/avatar.png
  18. BIN
      templates/app/react-native/assets/icon.png
  19. BIN
      templates/app/react-native/assets/logo.png
  20. BIN
      templates/app/react-native/assets/splash.png
  21. 6
      templates/app/react-native/babel.config.js
  22. 62
      templates/app/react-native/package.json
  23. 10
      templates/app/react-native/src/api/API.js
  24. 41
      templates/app/react-native/src/api/AccountAPI.js
  25. 30
      templates/app/react-native/src/api/ApplicationConfigurationAPI.js
  26. 26
      templates/app/react-native/src/api/IdentityAPI.js
  27. 21
      templates/app/react-native/src/api/TenantManagementAPI.js
  28. 97
      templates/app/react-native/src/components/AppContainer/AppContainer.js
  29. 139
      templates/app/react-native/src/components/DataList/DataList.js
  30. 121
      templates/app/react-native/src/components/DrawerContent/DrawerContent.js
  31. 82
      templates/app/react-native/src/components/FormButtons/FormButtons.js
  32. 63
      templates/app/react-native/src/components/Loading/Loading.js
  33. 31
      templates/app/react-native/src/components/LoadingButton/LoadingButton.js
  34. 19
      templates/app/react-native/src/components/MenuIcon/MenuIcon.js
  35. 128
      templates/app/react-native/src/components/TenantBox/TenantBox.js
  36. 18
      templates/app/react-native/src/components/ValidationMessage/ValidationMessage.js
  37. 3
      templates/app/react-native/src/contexts/LocalizationContext.js
  38. 17
      templates/app/react-native/src/hocs/PermissionHOC.js
  39. 16
      templates/app/react-native/src/hooks/UsePermission.js
  40. 102
      templates/app/react-native/src/interceptors/APIInterceptor.js
  41. 22
      templates/app/react-native/src/navigators/AuthNavigator.js
  42. 20
      templates/app/react-native/src/navigators/DrawerNavigator.js
  43. 24
      templates/app/react-native/src/navigators/HomeNavigator.js
  44. 41
      templates/app/react-native/src/navigators/SettingsNavigator.js
  45. 34
      templates/app/react-native/src/navigators/TenantsNavigator.js
  46. 32
      templates/app/react-native/src/navigators/UsersNavigator.js
  47. 99
      templates/app/react-native/src/screens/ChangePassword/ChangePasswordForm.js
  48. 33
      templates/app/react-native/src/screens/ChangePassword/ChangePasswordScreen.js
  49. 84
      templates/app/react-native/src/screens/CreateUpdateTenant/CreateUpdateTenantForm.js
  50. 77
      templates/app/react-native/src/screens/CreateUpdateTenant/CreateUpdateTenantScreen.js
  51. 242
      templates/app/react-native/src/screens/CreateUpdateUser/CreateUpdateUserForm.js
  52. 69
      templates/app/react-native/src/screens/CreateUpdateUser/CreateUpdateUserScreen.js
  53. 60
      templates/app/react-native/src/screens/CreateUpdateUser/UserRoles.js
  54. 34
      templates/app/react-native/src/screens/Home/HomeScreen.js
  55. 128
      templates/app/react-native/src/screens/Login/LoginScreen.js
  56. 122
      templates/app/react-native/src/screens/ManageProfile/ManageProfileForm.js
  57. 50
      templates/app/react-native/src/screens/ManageProfile/ManageProfileScreen.js
  58. 145
      templates/app/react-native/src/screens/Settings/SettingsScreen.js
  59. 59
      templates/app/react-native/src/screens/Tenants/TenantsScreen.js
  60. 61
      templates/app/react-native/src/screens/Users/UsersScreen.js
  61. 19
      templates/app/react-native/src/store/actions/AppActions.js
  62. 13
      templates/app/react-native/src/store/actions/LoadingActions.js
  63. 13
      templates/app/react-native/src/store/actions/PersistentStorageActions.js
  64. 25
      templates/app/react-native/src/store/index.js
  65. 12
      templates/app/react-native/src/store/reducers/AppReducer.js
  66. 23
      templates/app/react-native/src/store/reducers/LoadingReducer.js
  67. 17
      templates/app/react-native/src/store/reducers/PersistentStorageReducer.js
  68. 12
      templates/app/react-native/src/store/reducers/index.js
  69. 34
      templates/app/react-native/src/store/sagas/AppSaga.js
  70. 6
      templates/app/react-native/src/store/sagas/index.js
  71. 19
      templates/app/react-native/src/store/selectors/AppSelectors.js
  72. 11
      templates/app/react-native/src/store/selectors/LoadingSelectors.js
  73. 11
      templates/app/react-native/src/store/selectors/PersistentStorageSelectors.js
  74. 39
      templates/app/react-native/src/theme/components/Badge.js
  75. 11
      templates/app/react-native/src/theme/components/Body.js
  76. 384
      templates/app/react-native/src/theme/components/Button.js
  77. 37
      templates/app/react-native/src/theme/components/Card.js
  78. 198
      templates/app/react-native/src/theme/components/CardItem.js
  79. 38
      templates/app/react-native/src/theme/components/CheckBox.js
  80. 17
      templates/app/react-native/src/theme/components/Container.js
  81. 14
      templates/app/react-native/src/theme/components/Content.js
  82. 25
      templates/app/react-native/src/theme/components/Fab.js
  83. 117
      templates/app/react-native/src/theme/components/Footer.js
  84. 78
      templates/app/react-native/src/theme/components/FooterTab.js
  85. 86
      templates/app/react-native/src/theme/components/Form.js
  86. 13
      templates/app/react-native/src/theme/components/H1.js
  87. 13
      templates/app/react-native/src/theme/components/H2.js
  88. 13
      templates/app/react-native/src/theme/components/H3.js
  89. 389
      templates/app/react-native/src/theme/components/Header.js
  90. 12
      templates/app/react-native/src/theme/components/Icon.js
  91. 19
      templates/app/react-native/src/theme/components/Input.js
  92. 132
      templates/app/react-native/src/theme/components/InputGroup.js
  93. 241
      templates/app/react-native/src/theme/components/Item.js
  94. 15
      templates/app/react-native/src/theme/components/Label.js
  95. 11
      templates/app/react-native/src/theme/components/Left.js
  96. 441
      templates/app/react-native/src/theme/components/ListItem.js
  97. 14
      templates/app/react-native/src/theme/components/Picker.android.js
  98. 7
      templates/app/react-native/src/theme/components/Picker.ios.js
  99. 14
      templates/app/react-native/src/theme/components/Picker.js
  100. 26
      templates/app/react-native/src/theme/components/Radio.js

52
docs/en/Application-Services.md

@ -201,7 +201,7 @@ See the [authorization document](Authorization.md) for more.
## CRUD Application Services
If you need to create a simple **CRUD application service** which has Create, Update, Delete and Get methods, you can use ABP's **base classes** to easily build your services. You can inherit from `CrudAppService`.
If you need to create a simple **CRUD application service** which has Create, Update, Delete and Get methods, you can use ABP's **base classes** to easily build your services. You can inherit from the `CrudAppService`.
### Example
@ -219,7 +219,9 @@ public interface IBookAppService :
}
````
* `ICrudAppService` has generic arguments to get the primary key type of the entity and the DTO types for the CRUD operations (it does not get the entity type since the entity type is not exposed to the clients use this interface).
`ICrudAppService` has generic arguments to get the primary key type of the entity and the DTO types for the CRUD operations (it does not get the entity type since the entity type is not exposed to the clients use this interface).
> Creating interface for an application service is a good practice, but not required by the ABP Framework. You can skip the interface part.
`ICrudAppService` declares the following methods:
@ -292,6 +294,52 @@ public class BookAppService :
`CrudAppService` implements all methods declared in the `ICrudAppService` interface. You can then add your own custom methods or override and customize base methods.
> `CrudAppService` has different versions gets different number of generic arguments. Use the one suitable for you.
### AbstractKeyCrudAppService
`CrudAppService` requires to have an Id property as the primary key of your entity. If you are using composite keys then you can not utilize it.
`AbstractKeyCrudAppService` implements the same `ICrudAppService` interface, but this time without making assumption about your primary key.
#### Example
Assume that you have a `District` entity with `CityId` and `Name` as a composite primary key. Using `AbstractKeyCrudAppService` requires to implement `DeleteByIdAsync` and `GetEntityByIdAsync` methods yourself:
````csharp
public class DistrictAppService
: AbstractKeyCrudAppService<District, DistrictDto, DistrictKey>
{
public DistrictAppService(IRepository<District> repository)
: base(repository)
{
}
protected override async Task DeleteByIdAsync(DistrictKey id)
{
await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name);
}
protected override async Task<District> GetEntityByIdAsync(DistrictKey id)
{
return await AsyncQueryableExecuter.FirstOrDefaultAsync(
Repository.Where(d => d.CityId == id.CityId && d.Name == id.Name)
);
}
}
````
This implementation requires you to create a class represents your composite key:
````csharp
public class DistrictKey
{
public Guid CityId { get; set; }
public string Name { get; set; }
}
````
## Lifetime
Lifetime of application services are [transient](Dependency-Injection.md) and they are automatically registered to the dependency injection system.

38
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs

@ -12,6 +12,7 @@ using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Http.ProxyScripting.Generators;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.Conventions
@ -81,7 +82,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
continue;
}
if (!TypeHelper.IsPrimitiveExtended(prm.ParameterInfo.ParameterType))
if (!TypeHelper.IsPrimitiveExtended(prm.ParameterInfo.ParameterType, includeEnums: true))
{
if (CanUseFormBodyBinding(action, prm))
{
@ -94,7 +95,15 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
protected virtual bool CanUseFormBodyBinding(ActionModel action, ParameterModel parameter)
{
if (_options.ConventionalControllers.FormBodyBindingIgnoredTypes.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType)))
//We want to use "id" as path parameter, not body!
if (parameter.ParameterName == "id")
{
return false;
}
if (_options.ConventionalControllers
.FormBodyBindingIgnoredTypes
.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType)))
{
return false;
}
@ -251,7 +260,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
if (!selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Any())
{
selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] {httpMethod}));
selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { httpMethod }));
}
}
}
@ -295,9 +304,24 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
var url = $"api/{rootPath}/{controllerNameInUrl.ToCamelCase()}";
//Add {id} path if needed
if (action.Parameters.Any(p => p.ParameterName == "id"))
var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id");
if (idParameterModel != null)
{
url += "/{id}";
if (TypeHelper.IsPrimitiveExtended(idParameterModel.ParameterType, includeEnums: true))
{
url += "/{id}";
}
else
{
var properties = idParameterModel
.ParameterType
.GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var property in properties)
{
url += "/{" + property.Name + "}";
}
}
}
//Add action name if needed
@ -341,7 +365,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
protected virtual string NormalizeUrlControllerName(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration)
{
if(configuration?.UrlControllerNameNormalizer == null)
if (configuration?.UrlControllerNameNormalizer == null)
{
return controllerName;
}
@ -364,7 +388,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
protected virtual bool IsEmptySelector(SelectorModel selector)
{
return selector.AttributeRouteModel == null
return selector.AttributeRouteModel == null
&& selector.ActionConstraints.IsNullOrEmpty()
&& selector.EndpointMetadata.IsNullOrEmpty();
}

6
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs

@ -5,36 +5,30 @@ namespace Volo.Abp.Application.Services
{
public interface ICrudAppService<TEntityDto, in TKey>
: ICrudAppService<TEntityDto, TKey, PagedAndSortedResultRequestDto>
where TEntityDto : IEntityDto<TKey>
{
}
public interface ICrudAppService<TEntityDto, in TKey, in TGetListInput>
: ICrudAppService<TEntityDto, TKey, TGetListInput, TEntityDto, TEntityDto>
where TEntityDto : IEntityDto<TKey>
{
}
public interface ICrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput>
: ICrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
where TEntityDto : IEntityDto<TKey>
{
}
public interface ICrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput>
: ICrudAppService<TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntityDto : IEntityDto<TKey>
{
}
public interface ICrudAppService<TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput>
: IApplicationService
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
Task<TGetOutputDto> GetAsync(TKey id);

341
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs

@ -0,0 +1,341 @@
using System;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Linq;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.Application.Services
{
public abstract class AbstractKeyCrudAppService<TEntity, TEntityDto, TKey>
: AbstractKeyCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
where TEntity : class, IEntity
{
protected AbstractKeyCrudAppService(IRepository<TEntity> repository)
: base(repository)
{
}
}
public abstract class AbstractKeyCrudAppService<TEntity, TEntityDto, TKey, TGetListInput>
: AbstractKeyCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TEntityDto, TEntityDto>
where TEntity : class, IEntity
{
protected AbstractKeyCrudAppService(IRepository<TEntity> repository)
: base(repository)
{
}
}
public abstract class AbstractKeyCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput>
: AbstractKeyCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
where TEntity : class, IEntity
{
protected AbstractKeyCrudAppService(IRepository<TEntity> repository)
: base(repository)
{
}
}
public abstract class AbstractKeyCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: AbstractKeyCrudAppService<TEntity, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity
{
protected AbstractKeyCrudAppService(IRepository<TEntity> repository)
: base(repository)
{
}
protected override TEntityDto MapToGetListOutputDto(TEntity entity)
{
return MapToGetOutputDto(entity);
}
}
public abstract class AbstractKeyCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: ApplicationService,
ICrudAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity
{
public IAsyncQueryableExecuter AsyncQueryableExecuter { get; set; }
protected IRepository<TEntity> Repository { get; }
protected virtual string GetPolicyName { get; set; }
protected virtual string GetListPolicyName { get; set; }
protected virtual string CreatePolicyName { get; set; }
protected virtual string UpdatePolicyName { get; set; }
protected virtual string DeletePolicyName { get; set; }
protected AbstractKeyCrudAppService(IRepository<TEntity> repository)
{
Repository = repository;
AsyncQueryableExecuter = DefaultAsyncQueryableExecuter.Instance;
}
public virtual async Task<TGetOutputDto> GetAsync(TKey id)
{
await CheckGetPolicyAsync();
var entity = await GetEntityByIdAsync(id);
return MapToGetOutputDto(entity);
}
public virtual async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
{
await CheckGetListPolicyAsync();
var query = CreateFilteredQuery(input);
var totalCount = await AsyncQueryableExecuter.CountAsync(query);
query = ApplySorting(query, input);
query = ApplyPaging(query, input);
var entities = await AsyncQueryableExecuter.ToListAsync(query);
return new PagedResultDto<TGetListOutputDto>(
totalCount,
entities.Select(MapToGetListOutputDto).ToList()
);
}
public virtual async Task<TGetOutputDto> CreateAsync(TCreateInput input)
{
await CheckCreatePolicyAsync();
var entity = MapToEntity(input);
TryToSetTenantId(entity);
await Repository.InsertAsync(entity, autoSave: true);
return MapToGetOutputDto(entity);
}
public virtual async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
{
await CheckUpdatePolicyAsync();
var entity = await GetEntityByIdAsync(id);
//TODO: Check if input has id different than given id and normalize if it's default value, throw ex otherwise
MapToEntity(input, entity);
await Repository.UpdateAsync(entity, autoSave: true);
return MapToGetOutputDto(entity);
}
public virtual async Task DeleteAsync(TKey id)
{
await CheckDeletePolicyAsync();
await DeleteByIdAsync(id);
}
protected abstract Task DeleteByIdAsync(TKey id);
protected abstract Task<TEntity> GetEntityByIdAsync(TKey id);
protected virtual async Task CheckGetPolicyAsync()
{
await CheckPolicyAsync(GetPolicyName);
}
protected virtual async Task CheckGetListPolicyAsync()
{
await CheckPolicyAsync(GetListPolicyName);
}
protected virtual async Task CheckCreatePolicyAsync()
{
await CheckPolicyAsync(CreatePolicyName);
}
protected virtual async Task CheckUpdatePolicyAsync()
{
await CheckPolicyAsync(UpdatePolicyName);
}
protected virtual async Task CheckDeletePolicyAsync()
{
await CheckPolicyAsync(DeletePolicyName);
}
/// <summary>
/// Should apply sorting if needed.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="input">The input.</param>
protected virtual IQueryable<TEntity> ApplySorting(IQueryable<TEntity> query, TGetListInput input)
{
//Try to sort query if available
if (input is ISortedResultRequest sortInput)
{
if (!sortInput.Sorting.IsNullOrWhiteSpace())
{
return query.OrderBy(sortInput.Sorting);
}
}
//IQueryable.Task requires sorting, so we should sort if Take will be used.
if (input is ILimitedResultRequest)
{
return ApplyDefaultSorting(query);
}
//No sorting
return query;
}
/// <summary>
/// Applies sorting if no sorting specified but a limited result requested.
/// </summary>
/// <param name="query">The query.</param>
protected virtual IQueryable<TEntity> ApplyDefaultSorting(IQueryable<TEntity> query)
{
if (typeof(TEntity).IsAssignableTo<ICreationAuditedObject>())
{
return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime);
}
throw new AbpException("No sorting specified but this query requires sorting. Override the ApplyDefaultSorting method for your application service derived from AbstractKeyCrudAppService!");
}
/// <summary>
/// Should apply paging if needed.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="input">The input.</param>
protected virtual IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> query, TGetListInput input)
{
//Try to use paging if available
if (input is IPagedResultRequest pagedInput)
{
return query.PageBy(pagedInput);
}
//Try to limit query result if available
if (input is ILimitedResultRequest limitedInput)
{
return query.Take(limitedInput.MaxResultCount);
}
//No paging
return query;
}
/// <summary>
/// This method should create <see cref="IQueryable{TEntity}"/> based on given input.
/// It should filter query if needed, but should not do sorting or paging.
/// Sorting should be done in <see cref="ApplySorting"/> and paging should be done in <see cref="ApplyPaging"/>
/// methods.
/// </summary>
/// <param name="input">The input.</param>
protected virtual IQueryable<TEntity> CreateFilteredQuery(TGetListInput input)
{
return Repository;
}
/// <summary>
/// Maps <see cref="TEntity"/> to <see cref="TGetOutputDto"/>.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual TGetOutputDto MapToGetOutputDto(TEntity entity)
{
return ObjectMapper.Map<TEntity, TGetOutputDto>(entity);
}
/// <summary>
/// Maps <see cref="TEntity"/> to <see cref="TGetListOutputDto"/>.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual TGetListOutputDto MapToGetListOutputDto(TEntity entity)
{
return ObjectMapper.Map<TEntity, TGetListOutputDto>(entity);
}
/// <summary>
/// Maps <see cref="TCreateInput"/> to <see cref="TEntity"/> to create a new entity.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual TEntity MapToEntity(TCreateInput createInput)
{
var entity = ObjectMapper.Map<TCreateInput, TEntity>(createInput);
SetIdForGuids(entity);
return entity;
}
/// <summary>
/// Sets Id value for the entity if <see cref="TKey"/> is <see cref="Guid"/>.
/// It's used while creating a new entity.
/// </summary>
protected virtual void SetIdForGuids(TEntity entity)
{
var entityWithGuidId = entity as IEntity<Guid>;
if (entityWithGuidId == null || entityWithGuidId.Id != Guid.Empty)
{
return;
}
EntityHelper.TrySetId(
entityWithGuidId,
() => GuidGenerator.Create(),
true
);
}
/// <summary>
/// Maps <see cref="TUpdateInput"/> to <see cref="TEntity"/> to update the entity.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual void MapToEntity(TUpdateInput updateInput, TEntity entity)
{
ObjectMapper.Map(updateInput, entity);
}
protected virtual void TryToSetTenantId(TEntity entity)
{
if (entity is IMultiTenant && HasTenantIdProperty(entity))
{
var tenantId = CurrentTenant.Id;
if (!tenantId.HasValue)
{
return;
}
var propertyInfo = entity.GetType().GetProperty(nameof(IMultiTenant.TenantId));
if (propertyInfo == null || propertyInfo.GetSetMethod(true) == null)
{
return;
}
propertyInfo.SetValue(entity, tenantId);
}
}
protected virtual bool HasTenantIdProperty(TEntity entity)
{
return entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)) != null;
}
}
}

262
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs

@ -1,13 +1,10 @@
using System;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Linq;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.Application.Services
{
@ -65,274 +62,49 @@ namespace Volo.Abp.Application.Services
}
public abstract class CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: ApplicationService,
ICrudAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
: AbstractKeyCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
public IAsyncQueryableExecuter AsyncQueryableExecuter { get; set; }
protected IRepository<TEntity, TKey> Repository { get; }
protected virtual string GetPolicyName { get; set; }
protected virtual string GetListPolicyName { get; set; }
protected virtual string CreatePolicyName { get; set; }
protected virtual string UpdatePolicyName { get; set; }
protected virtual string DeletePolicyName { get; set; }
protected new IRepository<TEntity, TKey> Repository { get; }
protected CrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{
Repository = repository;
AsyncQueryableExecuter = DefaultAsyncQueryableExecuter.Instance;
}
public virtual async Task<TGetOutputDto> GetAsync(TKey id)
protected override async Task DeleteByIdAsync(TKey id)
{
await CheckGetPolicyAsync();
var entity = await GetEntityByIdAsync(id);
return MapToGetOutputDto(entity);
}
public virtual async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
{
await CheckGetListPolicyAsync();
var query = CreateFilteredQuery(input);
var totalCount = await AsyncQueryableExecuter.CountAsync(query);
query = ApplySorting(query, input);
query = ApplyPaging(query, input);
var entities = await AsyncQueryableExecuter.ToListAsync(query);
return new PagedResultDto<TGetListOutputDto>(
totalCount,
entities.Select(MapToGetListOutputDto).ToList()
);
}
public virtual async Task<TGetOutputDto> CreateAsync(TCreateInput input)
{
await CheckCreatePolicyAsync();
var entity = MapToEntity(input);
TryToSetTenantId(entity);
await Repository.InsertAsync(entity, autoSave: true);
return MapToGetOutputDto(entity);
}
public virtual async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
{
await CheckUpdatePolicyAsync();
var entity = await GetEntityByIdAsync(id);
//TODO: Check if input has id different than given id and normalize if it's default value, throw ex otherwise
MapToEntity(input, entity);
await Repository.UpdateAsync(entity, autoSave: true);
return MapToGetOutputDto(entity);
}
public virtual async Task DeleteAsync(TKey id)
{
await CheckDeletePolicyAsync();
await Repository.DeleteAsync(id);
}
protected virtual Task<TEntity> GetEntityByIdAsync(TKey id)
{
return Repository.GetAsync(id);
}
protected virtual async Task CheckGetPolicyAsync()
{
await CheckPolicyAsync(GetPolicyName);
}
protected virtual async Task CheckGetListPolicyAsync()
{
await CheckPolicyAsync(GetListPolicyName);
}
protected virtual async Task CheckCreatePolicyAsync()
{
await CheckPolicyAsync(CreatePolicyName);
}
protected virtual async Task CheckUpdatePolicyAsync()
{
await CheckPolicyAsync(UpdatePolicyName);
}
protected virtual async Task CheckDeletePolicyAsync()
protected override async Task<TEntity> GetEntityByIdAsync(TKey id)
{
await CheckPolicyAsync(DeletePolicyName);
return await Repository.GetAsync(id);
}
/// <summary>
/// Should apply sorting if needed.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="input">The input.</param>
protected virtual IQueryable<TEntity> ApplySorting(IQueryable<TEntity> query, TGetListInput input)
{
//Try to sort query if available
if (input is ISortedResultRequest sortInput)
{
if (!sortInput.Sorting.IsNullOrWhiteSpace())
{
return query.OrderBy(sortInput.Sorting);
}
}
//IQueryable.Task requires sorting, so we should sort if Take will be used.
if (input is ILimitedResultRequest)
{
return query.OrderByDescending(e => e.Id);
}
//No sorting
return query;
}
/// <summary>
/// Should apply paging if needed.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="input">The input.</param>
protected virtual IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> query, TGetListInput input)
{
//Try to use paging if available
if (input is IPagedResultRequest pagedInput)
{
return query.PageBy(pagedInput);
}
//Try to limit query result if available
if (input is ILimitedResultRequest limitedInput)
{
return query.Take(limitedInput.MaxResultCount);
}
//No paging
return query;
}
/// <summary>
/// This method should create <see cref="IQueryable{TEntity}"/> based on given input.
/// It should filter query if needed, but should not do sorting or paging.
/// Sorting should be done in <see cref="ApplySorting"/> and paging should be done in <see cref="ApplyPaging"/>
/// methods.
/// </summary>
/// <param name="input">The input.</param>
protected virtual IQueryable<TEntity> CreateFilteredQuery(TGetListInput input)
{
return Repository;
}
/// <summary>
/// Maps <see cref="TEntity"/> to <see cref="TGetOutputDto"/>.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual TGetOutputDto MapToGetOutputDto(TEntity entity)
{
return ObjectMapper.Map<TEntity, TGetOutputDto>(entity);
}
/// <summary>
/// Maps <see cref="TEntity"/> to <see cref="TGetListOutputDto"/>.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual TGetListOutputDto MapToGetListOutputDto(TEntity entity)
{
return ObjectMapper.Map<TEntity, TGetListOutputDto>(entity);
}
/// <summary>
/// Maps <see cref="TCreateInput"/> to <see cref="TEntity"/> to create a new entity.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual TEntity MapToEntity(TCreateInput createInput)
{
var entity = ObjectMapper.Map<TCreateInput, TEntity>(createInput);
SetIdForGuids(entity);
return entity;
}
/// <summary>
/// Sets Id value for the entity if <see cref="TKey"/> is <see cref="Guid"/>.
/// It's used while creating a new entity.
/// </summary>
protected virtual void SetIdForGuids(TEntity entity)
{
var entityWithGuidId = entity as IEntity<Guid>;
if (entityWithGuidId == null || entityWithGuidId.Id != Guid.Empty)
{
return;
}
EntityHelper.TrySetId(
entityWithGuidId,
() => GuidGenerator.Create(),
true
);
}
/// <summary>
/// Maps <see cref="TUpdateInput"/> to <see cref="TEntity"/> to update the entity.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual void MapToEntity(TUpdateInput updateInput, TEntity entity)
protected override void MapToEntity(TUpdateInput updateInput, TEntity entity)
{
if (updateInput is IEntityDto<TKey> entityDto)
{
entityDto.Id = entity.Id;
}
ObjectMapper.Map(updateInput, entity);
base.MapToEntity(updateInput, entity);
}
protected virtual void TryToSetTenantId(TEntity entity)
protected override IQueryable<TEntity> ApplyDefaultSorting(IQueryable<TEntity> query)
{
if (entity is IMultiTenant && HasTenantIdProperty(entity))
if (typeof(TEntity).IsAssignableTo<ICreationAuditedObject>())
{
var tenantId = CurrentTenant.Id;
if (!tenantId.HasValue)
{
return;
}
var propertyInfo = entity.GetType().GetProperty(nameof(IMultiTenant.TenantId));
if (propertyInfo == null || propertyInfo.GetSetMethod(true) == null)
{
return;
}
propertyInfo.SetValue(entity, tenantId);
return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime);
}
else
{
return query.OrderByDescending(e => e.Id);
}
}
protected virtual bool HasTenantIdProperty(TEntity entity)
{
return entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)) != null;
}
}
}

6
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityNotFoundException.cs

@ -47,7 +47,11 @@ namespace Volo.Abp.Domain.Entities
/// Creates a new <see cref="EntityNotFoundException"/> object.
/// </summary>
public EntityNotFoundException(Type entityType, object id, Exception innerException)
: base($"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", innerException)
: base(
id == null
? $"There is no such an entity given given id. Entity type: {entityType.FullName}"
: $"There is no such an entity. Entity type: {entityType.FullName}, id: {id}",
innerException)
{
EntityType = entityType;
Id = id;

30
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictAppService.cs

@ -0,0 +1,30 @@
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.TestApp.Application.Dto;
namespace Volo.Abp.TestApp.Application
{
//This is especially used to test the AbstractKeyCrudAppService
public class DistrictAppService : AbstractKeyCrudAppService<District, DistrictDto, DistrictKey>
{
public DistrictAppService(IRepository<District> repository)
: base(repository)
{
}
protected override async Task DeleteByIdAsync(DistrictKey id)
{
await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name);
}
protected override async Task<District> GetEntityByIdAsync(DistrictKey id)
{
return await AsyncQueryableExecuter.FirstOrDefaultAsync(
Repository.Where(d => d.CityId == id.CityId && d.Name == id.Name)
);
}
}
}

11
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictKey.cs

@ -0,0 +1,11 @@
using System;
namespace Volo.Abp.TestApp.Application
{
public class DistrictKey
{
public Guid CityId { get; set; }
public string Name { get; set; }
}
}

14
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/DistrictDto.cs

@ -0,0 +1,14 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Volo.Abp.TestApp.Application.Dto
{
public class DistrictDto : EntityDto
{
public Guid CityId { get; set; }
public string Name { get; set; }
public int Population { get; set; }
}
}

25
templates/app/react-native/.eslintrc.json

@ -0,0 +1,25 @@
{
"extends": ["airbnb", "prettier", "prettier/react"],
"parser": "babel-eslint",
"env": {
"jest": true
},
"rules": {
"no-use-before-define": 0,
"react/jsx-filename-extension": 0,
"react/prop-types": ["error", { "ignore": ["navigation", "children"] }],
"react/require-default-props": 0,
"react/jsx-props-no-spreading": 0,
"react/forbid-prop-types": 0,
"import/prefer-default-export": 0,
"comma-dangle": 0,
"no-underscore-dangle": 1,
"no-plusplus": ["error", { "allowForLoopAfterthoughts": true }],
"no-param-reassign": 0,
"operator-linebreak": 0,
"global-require": 0
},
"globals": {
"fetch": false
}
}

14
templates/app/react-native/.gitignore

@ -0,0 +1,14 @@
node_modules/**/*
.expo/*
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
web-report/
# macOS
.DS_Store

8
templates/app/react-native/.prettierrc

@ -0,0 +1,8 @@
{
"trailingComma": "all",
"singleQuote": true,
"jsxSingleQuote": false,
"printWidth": 100,
"semi": true,
"jsxBracketSameLine": true
}

3
templates/app/react-native/.vscode/extensions.json

@ -0,0 +1,3 @@
{
"recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
}

25
templates/app/react-native/App.js

@ -0,0 +1,25 @@
import { StyleProvider } from 'native-base';
import React from 'react';
import { enableScreens } from 'react-native-screens';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import AppContainer from './src/components/AppContainer/AppContainer';
import { store, persistor } from './src/store';
import getTheme from './src/theme/components';
import { activeTheme } from './src/theme/variables';
import { initAPIInterceptor } from './src/interceptors/APIInterceptor';
enableScreens();
initAPIInterceptor(store);
export default function App() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<StyleProvider style={getTheme(activeTheme)}>
<AppContainer />
</StyleProvider>
</PersistGate>
</Provider>
);
}

31
templates/app/react-native/Environment.js

@ -0,0 +1,31 @@
const ENV = {
dev: {
apiUrl: 'http://localhost:44305',
oAuthConfig: {
issuer: 'http://localhost:44305',
clientId: 'MyProjectName_App',
clientSecret: '1q2w3e*',
scope: 'MyProjectName',
},
localization: {
defaultResourceName: 'MyProjectName',
},
},
prod: {
apiUrl: 'http://localhost:44305',
oAuthConfig: {
issuer: 'http://localhost:44305',
clientId: 'MyProjectName_App',
clientSecret: '1q2w3e*',
scope: 'MyProjectName',
},
localization: {
defaultResourceName: 'MyProjectName',
},
},
};
export const getEnvVars = () => {
// eslint-disable-next-line no-undef
return __DEV__ ? ENV.dev : ENV.prod;
};

30
templates/app/react-native/app.json

@ -0,0 +1,30 @@
{
"expo": {
"name": "MyProjectName",
"slug": "MyProjectName",
"privacy": "public",
"sdkVersion": "36.0.0",
"platforms": ["ios", "android", "web"],
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "cover",
"backgroundColor": "#38003c"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.MyCompanyName.MyProjectName",
"buildNumber": "1.0.0"
},
"android": {
"package": "com.MyCompanyName.MyProjectName",
"versionCode": 1
}
}
}

BIN
templates/app/react-native/assets/avatar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

BIN
templates/app/react-native/assets/icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
templates/app/react-native/assets/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
templates/app/react-native/assets/splash.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

6
templates/app/react-native/babel.config.js

@ -0,0 +1,6 @@
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
};
};

62
templates/app/react-native/package.json

@ -0,0 +1,62 @@
{
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject",
"lint": "eslint *.js **/*.js",
"lint:fix": "yarn lint --fix"
},
"dependencies": {
"@expo/vector-icons": "^10.0.6",
"@react-native-community/masked-view": "0.1.5",
"@react-navigation/drawer": "^5.1.1",
"@react-navigation/native": "^5.0.9",
"@react-navigation/stack": "^5.1.1",
"@reduxjs/toolkit": "^1.2.3",
"axios": "^0.19.2",
"color": "^3.1.2",
"expo": "~36.0.0",
"expo-constants": "~8.0.0",
"expo-font": "~8.0.0",
"formik": "^2.1.2",
"i18n-js": "^3.5.1",
"lodash": "^4.17.15",
"native-base": "^2.13.8",
"prop-types": "^15.7.2",
"react": "~16.9.0",
"react-dom": "~16.9.0",
"react-native": "https://github.com/expo/react-native/archive/sdk-36.0.0.tar.gz",
"react-native-gesture-handler": "~1.5.0",
"react-native-reanimated": "~1.4.0",
"react-native-safe-area-context": "0.6.0",
"react-native-safe-area-view": "^1.0.0",
"react-native-screens": "2.0.0-alpha.12",
"react-native-web": "~0.11.7",
"react-redux": "^7.1.3",
"redux-persist": "^6.0.0",
"redux-saga": "^1.1.3",
"reselect": "^4.0.0",
"yup": "^0.28.0"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@types/i18n-js": "^3.0.1",
"@types/react": "~16.9.0",
"@types/react-native": "~0.60.23",
"@types/react-redux": "^7.1.7",
"@types/yup": "^0.26.29",
"babel-eslint": "^10.0.3",
"babel-preset-expo": "~8.0.0",
"eslint": "^6.8.0",
"eslint-config-airbnb": "^18.0.1",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.18.3",
"prettier": "^1.19.1"
},
"private": true
}

10
templates/app/react-native/src/api/API.js

@ -0,0 +1,10 @@
import axios from 'axios';
import { getEnvVars } from '../../Environment';
const { apiUrl } = getEnvVars();
const axiosInstance = axios.create({
baseURL: apiUrl,
});
export default axiosInstance;

41
templates/app/react-native/src/api/AccountAPI.js

@ -0,0 +1,41 @@
import api from './API';
import { getEnvVars } from '../../Environment';
const { oAuthConfig } = getEnvVars();
export const login = ({ username, password }) => {
// eslint-disable-next-line no-undef
const formData = new FormData();
formData.append('username', username);
formData.append('password', password);
formData.append('grant_type', 'password');
formData.append('scope', `${oAuthConfig.scope} offline_access`);
formData.append('client_id', oAuthConfig.clientId);
formData.append('client_secret', oAuthConfig.clientSecret);
return api({
method: 'POST',
url: '/connect/token',
headers: { 'Content-Type': 'multipart/form-data' },
data: formData,
baseURL: oAuthConfig.issuer,
}).then(({ data }) => data);
};
export const logout = () =>
api({
method: 'GET',
url: '/api/account/logout',
}).then(({ data }) => data);
export const getTenant = tenantName =>
api({
method: 'GET',
url: `/api/abp/multi-tenancy/tenants/by-name/${tenantName}`,
}).then(({ data }) => data);
export const getTenantById = tenantId =>
api({
method: 'GET',
url: `/api/abp/multi-tenancy/tenants/by-id/${tenantId}`,
}).then(({ data }) => data);

30
templates/app/react-native/src/api/ApplicationConfigurationAPI.js

@ -0,0 +1,30 @@
import i18n from 'i18n-js';
import api from './API';
export const getApplicationConfiguration = () =>
api
.get('/api/abp/application-configuration')
.then(({ data }) => data)
.then(async config => {
const { cultureName } = config.localization.currentCulture;
i18n.locale = cultureName;
Object.keys(config.localization.values).forEach(key => {
const resource = config.localization.values[key];
if (typeof resource !== 'object') return;
Object.keys(resource).forEach(key2 => {
if (/'{|{/g.test(resource[key2])) {
resource[key2] = resource[key2].replace(/'{|{/g, '{{').replace(/}'|}/g, '}}');
}
});
});
i18n.translations[cultureName] = {
...config.localization.values,
...(i18n.translations[cultureName] || {}),
};
return config;
});

26
templates/app/react-native/src/api/IdentityAPI.js

@ -0,0 +1,26 @@
import api from './API';
export const getProfileDetail = () => api.get('/api/identity/my-profile').then(({ data }) => data);
export const getAllRoles = () => api.get('/api/identity/roles/all').then(({ data }) => data.items);
export const getUserRoles = id =>
api.get(`/api/identity/users/${id}/roles`).then(({ data }) => data.items);
export const getUsers = (params = { maxResultCount: 10, skipCount: 0 }) =>
api.get('/api/identity/users', { params }).then(({ data }) => data);
export const getUserById = id => api.get(`/api/identity/users/${id}`).then(({ data }) => data);
export const createUser = body => api.post('/api/identity/users', body).then(({ data }) => data);
export const updateUser = (body, id) =>
api.put(`/api/identity/users/${id}`, body).then(({ data }) => data);
export const removeUser = id => api.delete(`/api/identity/users/${id}`);
export const updateProfileDetail = body =>
api.put('/api/identity/my-profile', body).then(({ data }) => data);
export const changePassword = body =>
api.post('/api/identity/my-profile/change-password', body).then(({ data }) => data);

21
templates/app/react-native/src/api/TenantManagementAPI.js

@ -0,0 +1,21 @@
import api from './API';
export function getTenants(params = {}) {
return api.get('/api/multi-tenancy/tenants', { params }).then(({ data }) => data);
}
export function createTenant(body) {
return api.post('/api/multi-tenancy/tenants', body).then(({ data }) => data);
}
export function getTenantById(id) {
return api.get(`/api/multi-tenancy/tenants/${id}`).then(({ data }) => data);
}
export function updateTenant(body, id) {
return api.put(`/api/multi-tenancy/tenants/${id}`, body).then(({ data }) => data);
}
export function removeTenant(id) {
return api.delete(`/api/multi-tenancy/tenants/${id}`).then(({ data }) => data);
}

97
templates/app/react-native/src/components/AppContainer/AppContainer.js

@ -0,0 +1,97 @@
import { Ionicons } from '@expo/vector-icons';
import * as Font from 'expo-font';
import i18n from 'i18n-js';
import PropTypes from 'prop-types';
import React, { useEffect, useState, useMemo } from 'react';
import { Platform, StatusBar } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { Root } from 'native-base';
import Loading from '../Loading/Loading';
import { connectToRedux } from '../../utils/ReduxConnect';
import { createLanguageSelector } from '../../store/selectors/AppSelectors';
import { createTokenSelector } from '../../store/selectors/PersistentStorageSelectors';
import AppActions from '../../store/actions/AppActions';
import PersistentStorageActions from '../../store/actions/PersistentStorageActions';
import { LocalizationContext } from '../../contexts/LocalizationContext';
import { isTokenValid } from '../../utils/TokenUtils';
import DrawerNavigator from '../../navigators/DrawerNavigator';
import AuthNavigator from '../../navigators/AuthNavigator';
import { getEnvVars } from '../../../Environment';
const { localization } = getEnvVars();
i18n.defaultSeparator = '::';
const cloneT = i18n.t;
i18n.t = (key, ...args) => {
if (key.slice(0, 2) === '::') {
key = localization.defaultResourceName + key;
}
return cloneT(key, ...args);
};
function AppContainer({ language, fetchAppConfig, token, setToken }) {
const platform = Platform.OS;
const [isReady, setIsReady] = useState(false);
const localizationContext = useMemo(
() => ({
t: i18n.t,
locale: (language || {}).cultureName,
}),
[language],
);
const isValid = useMemo(() => isTokenValid(token), [token]);
useEffect(() => {
if (!isValid && token && token.access_token) {
setToken({});
}
}, [isValid]);
useEffect(() => {
fetchAppConfig();
Font.loadAsync({
Roboto: require('native-base/Fonts/Roboto.ttf'),
Roboto_medium: require('native-base/Fonts/Roboto_medium.ttf'),
...Ionicons.font,
}).then(() => setIsReady(true));
}, []);
return (
<>
<StatusBar barStyle={platform === 'ios' ? 'dark-content' : 'light-content'} />
<Root>
{isReady && language ? (
<LocalizationContext.Provider value={localizationContext}>
<NavigationContainer>
{isValid ? <DrawerNavigator /> : <AuthNavigator />}
</NavigationContainer>
</LocalizationContext.Provider>
) : null}
</Root>
<Loading />
</>
);
}
AppContainer.propTypes = {
language: PropTypes.object,
token: PropTypes.object.isRequired,
fetchAppConfig: PropTypes.func.isRequired,
setToken: PropTypes.func.isRequired,
};
export default connectToRedux({
component: AppContainer,
stateProps: state => ({
language: createLanguageSelector()(state),
token: createTokenSelector()(state),
}),
dispatchProps: {
fetchAppConfig: AppActions.fetchAppConfigAsync,
setToken: PersistentStorageActions.setToken,
},
});

139
templates/app/react-native/src/components/DataList/DataList.js

@ -0,0 +1,139 @@
import { useFocusEffect } from '@react-navigation/native';
import i18n from 'i18n-js';
import { connectStyle, Icon, Input, InputGroup, Item, List, Spinner, Text } from 'native-base';
import PropTypes from 'prop-types';
import React, { forwardRef, useCallback, useEffect, useState } from 'react';
import { RefreshControl, StyleSheet, View } from 'react-native';
import LoadingActions from '../../store/actions/LoadingActions';
import { activeTheme } from '../../theme/variables';
import { debounce } from '../../utils/Debounce';
import { connectToRedux } from '../../utils/ReduxConnect';
import LoadingButton from '../LoadingButton/LoadingButton';
function DataList({
style,
navigation,
fetchFn,
render,
maxResultCount = 15,
debounceTime = 350,
...props
}) {
const [records, setRecords] = useState([]);
const [totalCount, setTotalCount] = useState(0);
const [loading, setLoading] = useState(false);
const [searchLoading, setSearchLoading] = useState(false);
const [buttonLoading, setButtonLoading] = useState(false);
const [skipCount, setSkipCount] = useState(0);
const [filter, setFilter] = useState('');
const fetch = (skip = 0, isRefreshingActive = true) => {
if (isRefreshingActive) setLoading(true);
return fetchFn({ filter, maxResultCount, skipCount: skip })
.then(({ items, totalCount: total }) => {
setTotalCount(total);
setRecords(skip ? [...records, ...items] : items);
setSkipCount(skip);
})
.finally(() => {
if (isRefreshingActive) setLoading(false);
});
};
const fetchPartial = () => {
if (loading || records.length === totalCount) return;
setButtonLoading(true);
fetch(skipCount + maxResultCount, false).finally(() => setButtonLoading(false));
};
useFocusEffect(
useCallback(() => {
setSkipCount(0);
fetch(0, false);
}, []),
);
useEffect(() => {
function searchFetch() {
setSearchLoading(true);
return fetch(0, false).finally(() => setTimeout(() => setSearchLoading(false), 150));
}
debounce(searchFetch, debounceTime)();
}, [filter]);
return (
<>
<Item placeholderLabel style={{ backgroundColor: '#fff' }}>
<InputGroup style={{ marginLeft: 10 }}>
<Input
placeholder={i18n.t('AbpUi::PagerSearch')}
style={{ padding: 0, margin: 0 }}
returnKeyType="done"
value={filter}
onChangeText={setFilter}
/>
{searchLoading ? (
<View>
<Spinner style={style.spinner} color={style.spinner.color} />
</View>
) : (
<Icon style={{ fontSize: 20, marginRight: 15 }} name="ios-search" />
)}
</InputGroup>
</Item>
<View style={style.container}>
<List
showsVerticalScrollIndicator
scrollEnabled
refreshControl={<RefreshControl refreshing={loading} onRefresh={fetch} />}
dataArray={records}
renderRow={(data, sectionID, rowId, ...args) => (
<>
{render(data, sectionID, rowId, ...args)}
{rowId + 1 === skipCount + maxResultCount && totalCount > records.length ? (
<View style={{ justifyContent: 'center', alignItems: 'center' }}>
<LoadingButton loading={buttonLoading} onPress={() => fetchPartial()}>
<Text>{i18n.t('AbpUi::LoadMore')}</Text>
</LoadingButton>
</View>
) : null}
</>
)}
{...props}
/>
</View>
</>
);
}
DataList.propTypes = {
...List.propTypes,
style: PropTypes.any.isRequired,
fetchFn: PropTypes.func.isRequired,
render: PropTypes.func.isRequired,
maxResultCount: PropTypes.number,
debounceTime: PropTypes.number,
};
const styles = StyleSheet.create({
container: { flex: 1 },
list: {},
spinner: {
transform: [{ scale: 0.5 }],
position: 'absolute',
right: 8,
top: -40,
color: activeTheme.brandInfo,
},
});
const Forwarded = forwardRef((props, ref) => <DataList {...props} forwardedRef={ref} />);
export default connectToRedux({
component: connectStyle('ABP.DataList', styles)(Forwarded),
dispatchProps: {
startLoading: LoadingActions.start,
stopLoading: LoadingActions.stop,
},
});

121
templates/app/react-native/src/components/DrawerContent/DrawerContent.js

@ -0,0 +1,121 @@
import { Text, View, List, ListItem, Left, Icon, Body } from 'native-base';
import React from 'react';
import { Image, StyleSheet } from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import i18n from 'i18n-js';
import PropTypes from 'prop-types';
import Constants from 'expo-constants';
import { withPermission } from '../../hocs/PermissionHOC';
const screens = {
Home: { label: '::Menu:Home', iconName: 'home' },
Users: {
label: 'AbpIdentity::Users',
iconName: 'contacts',
requiredPolicy: 'AbpIdentity.Users',
},
Tenants: {
label: 'AbpTenantManagement::Tenants',
iconName: 'people',
requiredPolicy: 'AbpTenantManagement.Tenants',
},
Settings: { label: 'AbpSettingManagement::Settings', iconName: 'cog' },
};
const ListItemWithPermission = withPermission(ListItem);
function DrawerContent({ navigation, state: { routeNames, index: currentScreenIndex } }) {
const navigate = screen => {
navigation.navigate(screen);
navigation.closeDrawer();
};
return (
<View style={styles.container}>
<SafeAreaView style={styles.container} forceInset={{ top: 'always', horizontal: 'never' }}>
<View style={styles.headerView}>
<Image style={styles.logo} source={require('../../../assets/logo.png')} />
</View>
<List
dataArray={routeNames}
keyExtractor={item => item}
renderRow={name => (
<ListItemWithPermission
icon
key={name}
policyKey={screens[name].requiredPolicy}
selected={name === routeNames[currentScreenIndex]}
onPress={() => navigate(name)}
style={{
...styles.navItem,
backgroundColor: name === routeNames[currentScreenIndex] ? '#38003c' : '#f2f2f2',
}}>
<Left>
<Icon
dark={name !== routeNames[currentScreenIndex]}
light={name === routeNames[currentScreenIndex]}
name={screens[name].iconName}
/>
</Left>
<Body style={{ borderBottomWidth: 0 }}>
<Text
style={{
color: name === routeNames[currentScreenIndex] ? '#fff' : '#000',
}}>
{i18n.t(screens[name].label)}
</Text>
</Body>
</ListItemWithPermission>
)}
/>
</SafeAreaView>
<View style={styles.footer}>
<Text note style={styles.copyRight}>
© MyProjectName
</Text>
<Text note style={styles.version}>
v{Constants.manifest.version}
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flexGrow: 1,
},
logo: {
marginTop: 20,
marginBottom: 15,
},
headerView: {
borderBottomWidth: 1,
borderColor: '#eee',
alignItems: 'center',
},
navItem: {
marginLeft: 0,
marginBottom: 3,
paddingLeft: 10,
width: '100%',
backgroundColor: '#f2f2f2',
},
footer: {
backgroundColor: '#eee',
flexDirection: 'row',
justifyContent: 'space-between',
},
copyRight: {
margin: 15,
},
version: {
margin: 15,
},
});
DrawerContent.propTypes = {
state: PropTypes.object.isRequired,
};
export default DrawerContent;

82
templates/app/react-native/src/components/FormButtons/FormButtons.js

@ -0,0 +1,82 @@
import React, { forwardRef } from 'react';
import PropTypes from 'prop-types';
import { Button, Text, connectStyle } from 'native-base';
import { View, StyleSheet, Alert } from 'react-native';
import i18n from 'i18n-js';
function FormButtons({
style,
submit,
remove,
removeMessage,
isRemoveDisabled,
isSubmitDisabled,
isShowRemove = false,
isShowSubmit = true,
}) {
const confirmation = () => {
Alert.alert(
i18n.t('AbpUi::AreYouSure'),
removeMessage,
[
{
text: i18n.t('AbpUi::Cancel'),
style: 'cancel',
},
{ text: i18n.t('AbpUi::Yes'), onPress: () => remove() },
],
{ cancelable: true },
);
};
return (
<View style={style.container}>
{isShowRemove ? (
<Button
abpButton
danger
style={{ flex: 1, borderRadius: 0 }}
onPress={() => confirmation()}
disabled={isRemoveDisabled}>
<Text>{i18n.t('AbpIdentity::Delete')}</Text>
</Button>
) : null}
{isShowSubmit ? (
<Button
abpButton
primary
style={{ flex: 1, borderRadius: 0 }}
onPress={submit}
disabled={isSubmitDisabled}>
<Text>{i18n.t('AbpIdentity::Save')}</Text>
</Button>
) : null}
</View>
);
}
FormButtons.propTypes = {
submit: PropTypes.func.isRequired,
remove: PropTypes.func,
removeMessage: PropTypes.string,
style: PropTypes.any,
isRemoveDisabled: PropTypes.bool,
isSubmitDisabled: PropTypes.bool,
isShowRemove: PropTypes.bool,
isShowSubmit: PropTypes.bool,
};
const styles = StyleSheet.create({
container: {
width: '100%',
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
bottom: 0,
flexDirection: 'row',
},
});
const Forwarded = forwardRef((props, ref) => <FormButtons {...props} forwardedRef={ref} />);
export default connectStyle('ABP.FormButtons', styles)(Forwarded);

63
templates/app/react-native/src/components/Loading/Loading.js

@ -0,0 +1,63 @@
import React, { forwardRef } from 'react';
import { Spinner, View, connectStyle } from 'native-base';
import { StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { activeTheme } from '../../theme/variables';
import { connectToRedux } from '../../utils/ReduxConnect';
import {
createLoadingSelector,
createOpacitySelector,
} from '../../store/selectors/LoadingSelectors';
function Loading({ style, loading, opacity }) {
return loading ? (
<View style={style.container}>
<View
style={{
...style.backdrop,
opacity: opacity || 0.6,
}}
/>
<Spinner style={style.spinner} color={style.spinner.color} />
</View>
) : null;
}
const Forwarded = forwardRef((props, ref) => <Loading {...props} forwardedRef={ref} />);
const backdropStyle = {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: '#fff',
};
export const styles = StyleSheet.create({
container: {
...backdropStyle,
backgroundColor: 'transparent',
zIndex: activeTheme.zIndex.indicator,
alignItems: 'center',
justifyContent: 'center',
},
backdrop: backdropStyle,
spinner: {
color: activeTheme.brandPrimary,
fontSize: 100,
},
});
Loading.propTypes = {
style: PropTypes.objectOf(PropTypes.any),
loading: PropTypes.bool,
opacity: PropTypes.number,
};
export default connectToRedux({
component: connectStyle('ABP.Loading', styles)(Forwarded),
stateProps: state => ({
loading: createLoadingSelector()(state),
opacity: createOpacitySelector()(state),
}),
});

31
templates/app/react-native/src/components/LoadingButton/LoadingButton.js

@ -0,0 +1,31 @@
import { Button, connectStyle, Spinner } from 'native-base';
import PropTypes from 'prop-types';
import React, { forwardRef } from 'react';
import { StyleSheet } from 'react-native';
function LoadingButton({ loading = false, style, children, ...props }) {
return (
<Button style={style.button} {...props}>
{children}
{loading ? <Spinner style={style.spinner} color={style.spinner.color || 'white'} /> : null}
</Button>
);
}
LoadingButton.propTypes = {
...Button.propTypes,
loading: PropTypes.bool.isRequired,
};
const styles = StyleSheet.create({
button: { marginTop: 20, marginBottom: 30, height: 30 },
spinner: {
transform: [{ scale: 0.5 }],
color: 'white',
marginRight: 5,
},
});
const Forwarded = forwardRef((props, ref) => <LoadingButton {...props} forwardedRef={ref} />);
export default connectStyle('ABP.LoadingButton', styles)(Forwarded);

19
templates/app/react-native/src/components/MenuIcon/MenuIcon.js

@ -0,0 +1,19 @@
import React from 'react';
import { TouchableOpacity } from 'react-native';
import { Icon } from 'native-base';
import PropTypes from 'prop-types';
function MenuIcon({ onPress, iconName = 'menu' }) {
return (
<TouchableOpacity onPress={onPress}>
<Icon navElement name={iconName} />
</TouchableOpacity>
);
}
MenuIcon.propTypes = {
onPress: PropTypes.func.isRequired,
iconName: PropTypes.string,
};
export default MenuIcon;

128
templates/app/react-native/src/components/TenantBox/TenantBox.js

@ -0,0 +1,128 @@
import i18n from 'i18n-js';
import {
Button,
connectStyle,
Content,
Input,
InputGroup,
Label,
Segment,
Text,
} from 'native-base';
import PropTypes from 'prop-types';
import React, { forwardRef, useState } from 'react';
import { StyleSheet, View, Alert } from 'react-native';
import { getTenant } from '../../api/AccountAPI';
import PersistentStorageActions from '../../store/actions/PersistentStorageActions';
import { connectToRedux } from '../../utils/ReduxConnect';
import { createTenantSelector } from '../../store/selectors/PersistentStorageSelectors';
function TenantBox({ style, tenant = {}, setTenant, showTenantSelection, toggleTenantSelection }) {
const [tenantName, setTenantName] = useState(tenant.name);
const findTenant = () => {
if (!tenantName) {
setTenant({});
toggleTenantSelection();
return;
}
getTenant(tenantName).then(({ success, ...data }) => {
if (!success) {
Alert.alert(
i18n.t('AbpUi::Error'),
i18n.t('AbpUiMultiTenancy::GivenTenantIsNotAvailable', {
0: tenantName,
}),
[{ text: i18n.t('AbpUi::Ok') }],
);
return;
}
setTenant(data);
toggleTenantSelection();
});
};
return (
<>
<Segment style={style.container}>
<View>
<Text style={style.title}>{i18n.t('AbpUiMultiTenancy::Tenant')}</Text>
<Text style={style.tenant}>
{tenant.name ? tenant.name : i18n.t('AbpUiMultiTenancy::NotSelected')}
</Text>
</View>
<Button
style={{ ...style.switchButton, display: !showTenantSelection ? 'flex' : 'none' }}
onPress={() => toggleTenantSelection()}>
<Text style={{ color: '#fff' }}>{i18n.t('AbpUiMultiTenancy::Switch')}</Text>
</Button>
</Segment>
{showTenantSelection ? (
<Content px20 style={{ flex: 1 }}>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpUiMultiTenancy::Name')}</Label>
<Input abpInput value={tenantName} onChangeText={setTenantName} />
</InputGroup>
<Text style={style.hint}>{i18n.t('AbpUiMultiTenancy::SwitchTenantHint')}</Text>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Button abpButton light style={style.button} onPress={() => toggleTenantSelection()}>
<Text>{i18n.t('AbpAccount::Cancel')}</Text>
</Button>
<Button abpButton style={style.button} onPress={() => findTenant()}>
<Text>{i18n.t('AbpAccount::Save')}</Text>
</Button>
</View>
</Content>
) : null}
</>
);
}
TenantBox.propTypes = {
style: PropTypes.any.isRequired,
setTenant: PropTypes.func.isRequired,
showTenantSelection: PropTypes.bool.isRequired,
toggleTenantSelection: PropTypes.func.isRequired,
tenant: PropTypes.object.isRequired,
};
const styles = StyleSheet.create({
container: {
paddingHorizontal: 20,
alignItems: 'center',
justifyContent: 'space-between',
height: 70,
},
button: { marginTop: 20, width: '49%' },
switchButton: {
borderTopWidth: 0,
borderRightWidth: 0,
borderBottomWidth: 0,
borderLeftWidth: 0,
borderRadius: 10,
backgroundColor: '#38003c',
height: 35,
},
tenant: { color: '#777' },
title: {
marginRight: 10,
color: '#777',
fontSize: 13,
fontWeight: '600',
textTransform: 'uppercase',
},
hint: { color: '#bbb', textAlign: 'left' },
});
const Forwarded = forwardRef((props, ref) => <TenantBox {...props} forwardedRef={ref} />);
export default connectToRedux({
component: connectStyle('ABP.TenantBox', styles)(Forwarded),
dispatchProps: {
setTenant: PersistentStorageActions.setTenant,
},
stateProps: state => ({
tenant: createTenantSelector()(state),
}),
});

18
templates/app/react-native/src/components/ValidationMessage/ValidationMessage.js

@ -0,0 +1,18 @@
import i18n from 'i18n-js';
import { connectStyle } from 'native-base';
import React, { forwardRef } from 'react';
import { Text } from 'react-native';
const ValidationMessage = ({ children, ...props }) =>
children ? <Text {...props}>{i18n.t(children)}</Text> : null;
const styles = {
fontSize: 12,
marginHorizontal: 10,
marginTop: -5,
color: '#ed2f2f',
};
const Forwarded = forwardRef((props, ref) => <ValidationMessage {...props} forwardedRef={ref} />);
export default connectStyle('ABP.ValidationMessage', styles)(Forwarded);

3
templates/app/react-native/src/contexts/LocalizationContext.js

@ -0,0 +1,3 @@
import React from 'react';
export const LocalizationContext = React.createContext();

17
templates/app/react-native/src/hocs/PermissionHOC.js

@ -0,0 +1,17 @@
import React, { forwardRef } from 'react';
import PropTypes from 'prop-types';
import { usePermission } from '../hooks/UsePermission';
export function withPermission(Component, policyKey) {
const Forwarded = forwardRef((props, ref) => {
const isGranted =
policyKey || props.policyKey ? usePermission(policyKey || props.policyKey) : true;
return isGranted ? <Component ref={ref} {...props} /> : null;
});
Forwarded.propTypes = {
policyKey: PropTypes.string,
};
return Forwarded;
}

16
templates/app/react-native/src/hooks/UsePermission.js

@ -0,0 +1,16 @@
import { useEffect, useState } from 'react';
import { store } from '../store';
import { createGrantedPolicySelector } from '../store/selectors/AppSelectors';
export function usePermission(key) {
const [permission, setPermission] = useState(false);
const state = store.getState();
const policy = createGrantedPolicySelector(key)(state);
useEffect(() => {
setPermission(policy);
}, [policy]);
return permission;
}

102
templates/app/react-native/src/interceptors/APIInterceptor.js

@ -0,0 +1,102 @@
import { Toast } from 'native-base';
import i18n from 'i18n-js';
import api from '../api/API';
import PersistentStorageActions from '../store/actions/PersistentStorageActions';
import LoadingActions from '../store/actions/LoadingActions';
export function initAPIInterceptor(store) {
api.interceptors.request.use(
async request => {
const {
persistentStorage: { token, language, tenant },
} = store.getState();
if (!request.headers.Authorization && token && token.access_token) {
request.headers.Authorization = `${token.token_type} ${token.access_token}`;
}
if (!request.headers['Content-Type']) {
request.headers['Content-Type'] = 'application/json';
}
if (!request.headers['Accept-Language'] && language) {
request.headers['Accept-Language'] = language;
}
if (!request.headers.__tenant && tenant && tenant.tenantId) {
request.headers.__tenant = tenant.tenantId;
}
return request;
},
error => console.error(error),
);
api.interceptors.response.use(
response => response,
error => {
store.dispatch(LoadingActions.clear());
const errorRes = error.response;
if (errorRes) {
if (errorRes.headers._abperrorformat && errorRes.status === 401) {
store.dispatch(PersistentStorageActions.setToken({}));
}
showError({ error: errorRes.data.error || {}, status: errorRes.status });
} else {
Toast.show({
text: 'An unexpected error has occurred',
buttonText: 'x',
duration: 10000,
type: 'danger',
textStyle: { textAlign: 'center' },
});
}
return Promise.reject(error);
},
);
}
function showError({ error = {}, status }) {
let message = '';
let title = i18n.t('AbpAccount::DefaultErrorMessage');
if (typeof error === 'string') {
message = error;
} else if (error.details) {
message = error.details;
title = error.message;
} else if (error.message) {
message = error.message;
} else {
switch (status) {
case 401:
title = i18n.t('AbpAccount::DefaultErrorMessage401');
message = i18n.t('AbpAccount::DefaultErrorMessage401Detail');
break;
case 403:
title = i18n.t('AbpAccount::DefaultErrorMessage403');
message = i18n.t('AbpAccount::DefaultErrorMessage403Detail');
break;
case 404:
title = i18n.t('AbpAccount::DefaultErrorMessage404');
message = i18n.t('AbpAccount::DefaultErrorMessage404Detail');
break;
case 500:
title = i18n.t('AbpAccount::500Message');
message = i18n.t('AbpAccount::InternalServerErrorMessage');
break;
default:
break;
}
}
Toast.show({
text: `${title}\n${message}`,
buttonText: 'x',
duration: 10000,
type: 'danger',
textStyle: { textAlign: 'center' },
});
}

22
templates/app/react-native/src/navigators/AuthNavigator.js

@ -0,0 +1,22 @@
import { createStackNavigator } from '@react-navigation/stack';
import React from 'react';
import { LocalizationContext } from '../contexts/LocalizationContext';
import LoginScreen from '../screens/Login/LoginScreen';
const Stack = createStackNavigator();
export default function AuthStackNavigator() {
const { t } = React.useContext(LocalizationContext);
return (
<Stack.Navigator initialRouteName="Login">
<Stack.Screen
name="Login"
component={LoginScreen}
options={() => ({
title: t('AbpAccount::Login'),
})}
/>
</Stack.Navigator>
);
}

20
templates/app/react-native/src/navigators/DrawerNavigator.js

@ -0,0 +1,20 @@
import React from 'react';
import { createDrawerNavigator } from '@react-navigation/drawer';
import HomeStackNavigator from './HomeNavigator';
import SettingsStackNavigator from './SettingsNavigator';
import UsersStackNavigator from './UsersNavigator';
import TenantsStackNavigator from './TenantsNavigator';
import DrawerContent from '../components/DrawerContent/DrawerContent';
const Drawer = createDrawerNavigator();
export default function DrawerNavigator() {
return (
<Drawer.Navigator initialRouteName="Home" drawerContent={DrawerContent}>
<Drawer.Screen name="Home" component={HomeStackNavigator} />
<Drawer.Screen name="Users" component={UsersStackNavigator} />
<Drawer.Screen name="Tenants" component={TenantsStackNavigator} />
<Drawer.Screen name="Settings" component={SettingsStackNavigator} />
</Drawer.Navigator>
);
}

24
templates/app/react-native/src/navigators/HomeNavigator.js

@ -0,0 +1,24 @@
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from '../screens/Home/HomeScreen';
import MenuIcon from '../components/MenuIcon/MenuIcon';
import { LocalizationContext } from '../contexts/LocalizationContext';
const Stack = createStackNavigator();
export default function HomeStackNavigator() {
const { t } = React.useContext(LocalizationContext);
return (
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
name="Home"
component={HomeScreen}
options={({ navigation }) => ({
headerLeft: () => <MenuIcon onPress={() => navigation.openDrawer()} />,
title: t('::Menu:Home'),
})}
/>
</Stack.Navigator>
);
}

41
templates/app/react-native/src/navigators/SettingsNavigator.js

@ -0,0 +1,41 @@
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import i18n from 'i18n-js';
import SettingsScreen from '../screens/Settings/SettingsScreen';
import ChangePasswordScreen from '../screens/ChangePassword/ChangePasswordScreen';
import ManageProfileScreen from '../screens/ManageProfile/ManageProfileScreen';
import MenuIcon from '../components/MenuIcon/MenuIcon';
import { LocalizationContext } from '../contexts/LocalizationContext';
const Stack = createStackNavigator();
export default function SettingsStackNavigator() {
const { t } = React.useContext(LocalizationContext);
return (
<Stack.Navigator initialRouteName="Settings">
<Stack.Screen
name="Settings"
component={SettingsScreen}
options={({ navigation }) => ({
headerLeft: () => <MenuIcon onPress={() => navigation.openDrawer()} />,
title: t('AbpSettingManagement::Settings'),
})}
/>
<Stack.Screen
name="ChangePassword"
component={ChangePasswordScreen}
options={{
title: i18n.t('AbpUi::ChangePassword'),
}}
/>
<Stack.Screen
name="ManageProfile"
component={ManageProfileScreen}
options={{
title: i18n.t('AbpAccount::ManageYourProfile'),
}}
/>
</Stack.Navigator>
);
}

34
templates/app/react-native/src/navigators/TenantsNavigator.js

@ -0,0 +1,34 @@
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import TenantsScreen from '../screens/Tenants/TenantsScreen';
import CreateUpdateTenantScreen from '../screens/CreateUpdateTenant/CreateUpdateTenantScreen';
import MenuIcon from '../components/MenuIcon/MenuIcon';
import { LocalizationContext } from '../contexts/LocalizationContext';
const Stack = createStackNavigator();
export default function TenantsStackNavigator() {
const { t } = React.useContext(LocalizationContext);
return (
<Stack.Navigator initialRouteName="Tenants">
<Stack.Screen
name="Tenants"
component={TenantsScreen}
options={({ navigation }) => ({
headerLeft: () => <MenuIcon onPress={() => navigation.openDrawer()} />,
title: t('AbpTenantManagement::Tenants'),
})}
/>
<Stack.Screen
name="CreateUpdateTenant"
component={CreateUpdateTenantScreen}
options={({ route }) => ({
title: t(
route.params?.tenantId ? 'AbpTenantManagement::Edit' : 'AbpTenantManagement::NewTenant',
),
})}
/>
</Stack.Navigator>
);
}

32
templates/app/react-native/src/navigators/UsersNavigator.js

@ -0,0 +1,32 @@
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import UsersScreen from '../screens/Users/UsersScreen';
import CreateUpdateUserScreen from '../screens/CreateUpdateUser/CreateUpdateUserScreen';
import MenuIcon from '../components/MenuIcon/MenuIcon';
import { LocalizationContext } from '../contexts/LocalizationContext';
const Stack = createStackNavigator();
export default function UsersStackNavigator() {
const { t } = React.useContext(LocalizationContext);
return (
<Stack.Navigator initialRouteName="Users">
<Stack.Screen
name="Users"
component={UsersScreen}
options={({ navigation }) => ({
headerLeft: () => <MenuIcon onPress={() => navigation.openDrawer()} />,
title: t('AbpIdentity::Users'),
})}
/>
<Stack.Screen
name="CreateUpdateUser"
component={CreateUpdateUserScreen}
options={({ route }) => ({
title: t(route.params?.userId ? 'AbpIdentity::Edit' : 'AbpIdentity::NewUser'),
})}
/>
</Stack.Navigator>
);
}

99
templates/app/react-native/src/screens/ChangePassword/ChangePasswordForm.js

@ -0,0 +1,99 @@
import { Formik } from 'formik';
import i18n from 'i18n-js';
import { Container, Content, Form, Input, InputGroup, Item, Icon, Label } from 'native-base';
import PropTypes from 'prop-types';
import React, { useRef, useState } from 'react';
import * as Yup from 'yup';
import FormButtons from '../../components/FormButtons/FormButtons';
import ValidationMessage from '../../components/ValidationMessage/ValidationMessage';
const ValidationSchema = Yup.object().shape({
currentPassword: Yup.string().required('AbpAccount::ThisFieldIsRequired.'),
newPassword: Yup.string().required('AbpAccount::ThisFieldIsRequired.'),
});
function ChangePasswordForm({ submit, cancel }) {
const [showCurrentPassword, setShowCurrentPassword] = useState(false);
const [showNewPassword, setShowNewPassword] = useState(false);
const currentPasswordRef = useRef();
const newPasswordRef = useRef();
const onSubmit = values => {
submit({
...values,
newPasswordConfirm: values.newPassword,
});
};
return (
<Formik
enableReinitialize
validationSchema={ValidationSchema}
initialValues={{
currentPassword: '',
newPassword: '',
}}
onSubmit={values => onSubmit(values)}>
{({ handleChange, handleBlur, handleSubmit, values, errors, isValid }) => (
<>
<Container>
<Content px20>
<Form>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpIdentity::DisplayName:CurrentPassword')}</Label>
<Item abpInput>
<Input
ref={currentPasswordRef}
onSubmitEditing={() => newPasswordRef.current._root.focus()}
returnKeyType="next"
onChangeText={handleChange('currentPassword')}
onBlur={handleBlur('currentPassword')}
value={values.currentPassword}
textContentType="password"
secureTextEntry={!showCurrentPassword}
/>
<Icon
active
name={showCurrentPassword ? 'eye-off' : 'eye'}
onPress={() => setShowCurrentPassword(!showCurrentPassword)}
/>
</Item>
</InputGroup>
<ValidationMessage>{errors.currentPassword}</ValidationMessage>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpIdentity::DisplayName:NewPassword')}</Label>
<Item abpInput>
<Input
ref={newPasswordRef}
returnKeyType="done"
onSubmitEditing={handleSubmit}
onChangeText={handleChange('newPassword')}
onBlur={handleBlur('newPassword')}
value={values.newPassword}
textContentType="newPassword"
secureTextEntry={!showNewPassword}
/>
<Icon
name={showNewPassword ? 'eye-off' : 'eye'}
onPress={() => setShowNewPassword(!showNewPassword)}
/>
</Item>
</InputGroup>
<ValidationMessage>{errors.newPassword}</ValidationMessage>
</Form>
</Content>
</Container>
<FormButtons submit={handleSubmit} cancel={cancel} isSubmitDisabled={!isValid} />
</>
)}
</Formik>
);
}
ChangePasswordForm.propTypes = {
submit: PropTypes.func.isRequired,
cancel: PropTypes.func.isRequired,
};
export default ChangePasswordForm;

33
templates/app/react-native/src/screens/ChangePassword/ChangePasswordScreen.js

@ -0,0 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
import { changePassword } from '../../api/IdentityAPI';
import LoadingActions from '../../store/actions/LoadingActions';
import { connectToRedux } from '../../utils/ReduxConnect';
import ChangePasswordForm from './ChangePasswordForm';
function ChangePasswordScreen({ navigation, startLoading, stopLoading }) {
const submit = data => {
startLoading({ key: 'changePassword' });
changePassword(data)
.then(() => {
navigation.goBack();
})
.finally(() => stopLoading({ key: 'changePassword' }));
};
return <ChangePasswordForm submit={submit} cancel={() => navigation.goBack()} />;
}
ChangePasswordScreen.propTypes = {
startLoading: PropTypes.func.isRequired,
stopLoading: PropTypes.func.isRequired,
};
export default connectToRedux({
component: ChangePasswordScreen,
dispatchProps: {
startLoading: LoadingActions.start,
stopLoading: LoadingActions.stop,
},
});

84
templates/app/react-native/src/screens/CreateUpdateTenant/CreateUpdateTenantForm.js

@ -0,0 +1,84 @@
import { Formik } from 'formik';
import i18n from 'i18n-js';
import { Container, Content, Input, InputGroup, Label } from 'native-base';
import PropTypes from 'prop-types';
import React, { useRef } from 'react';
import { StyleSheet } from 'react-native';
import * as Yup from 'yup';
import FormButtons from '../../components/FormButtons/FormButtons';
import ValidationMessage from '../../components/ValidationMessage/ValidationMessage';
import { usePermission } from '../../hooks/UsePermission';
const validations = {
name: Yup.string().required('AbpAccount::ThisFieldIsRequired.'),
};
function CreateUpdateTenantForm({ editingTenant = {}, submit, remove }) {
const tenantNameRef = useRef();
const hasRemovePermission = usePermission('AbpTenantManagement.Tenants.Delete');
const onSubmit = values => {
submit({
...editingTenant,
...values,
});
};
return (
<Formik
enableReinitialize
validationSchema={Yup.object().shape({
...validations,
})}
initialValues={{
lockoutEnabled: false,
twoFactorEnabled: false,
...editingTenant,
}}
onSubmit={values => onSubmit(values)}>
{({ handleChange, handleBlur, handleSubmit, values, errors, isValid }) => (
<>
<Container style={styles.container}>
<Content px20>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpTenantManagement::TenantName')}</Label>
<Input
abpInput
ref={tenantNameRef}
onChangeText={handleChange('name')}
onBlur={handleBlur('name')}
value={values.name}
/>
</InputGroup>
<ValidationMessage>{errors.name}</ValidationMessage>
</Content>
</Container>
<FormButtons
submit={handleSubmit}
remove={remove}
removeMessage={i18n.t('AbpTenantManagement::TenantDeletionConfirmationMessage', {
0: editingTenant.name,
})}
isSubmitDisabled={!isValid}
isShowRemove={!!editingTenant.id && hasRemovePermission}
/>
</>
)}
</Formik>
);
}
CreateUpdateTenantForm.propTypes = {
editingTenant: PropTypes.object,
submit: PropTypes.func.isRequired,
remove: PropTypes.func.isRequired,
};
const styles = StyleSheet.create({
container: {
marginBottom: 50,
},
});
export default CreateUpdateTenantForm;

77
templates/app/react-native/src/screens/CreateUpdateTenant/CreateUpdateTenantScreen.js

@ -0,0 +1,77 @@
import PropTypes from 'prop-types';
import React, { useState, useCallback } from 'react';
import { useFocusEffect } from '@react-navigation/native';
import {
createTenant,
getTenantById,
removeTenant,
updateTenant,
} from '../../api/TenantManagementAPI';
import LoadingActions from '../../store/actions/LoadingActions';
import { createLoadingSelector } from '../../store/selectors/LoadingSelectors';
import { connectToRedux } from '../../utils/ReduxConnect';
import CreateUpdateTenantForm from './CreateUpdateTenantForm';
function CreateUpdateTenantScreen({ navigation, route, startLoading, stopLoading }) {
const [tenant, setTenant] = useState();
const tenantId = route.params?.tenantId;
const remove = () => {
startLoading({ key: 'removeTenant' });
removeTenant(tenantId)
.then(() => navigation.goBack())
.finally(() => stopLoading({ key: 'removeTenant' }));
};
useFocusEffect(
useCallback(() => {
if (tenantId) {
getTenantById(tenantId).then((data = {}) => setTenant(data));
}
}, []),
);
const submit = data => {
startLoading({ key: 'saveTenant' });
let request;
if (data.id) {
request = updateTenant(data, tenantId);
} else {
request = createTenant(data);
}
request
.then(() => {
navigation.goBack();
})
.finally(() => stopLoading({ key: 'saveTenant' }));
};
const renderForm = () => (
<CreateUpdateTenantForm editingTenant={tenant} submit={submit} remove={remove} />
);
if (tenantId && tenant) {
return renderForm();
}
if (!tenantId) {
return renderForm();
}
return null;
}
CreateUpdateTenantScreen.propTypes = {
startLoading: PropTypes.func.isRequired,
stopLoading: PropTypes.func.isRequired,
};
export default connectToRedux({
component: CreateUpdateTenantScreen,
stateProps: state => ({ loading: createLoadingSelector()(state) }),
dispatchProps: {
startLoading: LoadingActions.start,
stopLoading: LoadingActions.stop,
},
});

242
templates/app/react-native/src/screens/CreateUpdateUser/CreateUpdateUserForm.js

@ -0,0 +1,242 @@
import { Formik } from 'formik';
import i18n from 'i18n-js';
import {
Body,
Button,
CheckBox,
Container,
Content,
Input,
InputGroup,
Item,
Icon,
Label,
ListItem,
Segment,
Text,
} from 'native-base';
import PropTypes from 'prop-types';
import React, { useRef, useState } from 'react';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import * as Yup from 'yup';
import FormButtons from '../../components/FormButtons/FormButtons';
import ValidationMessage from '../../components/ValidationMessage/ValidationMessage';
import { usePermission } from '../../hooks/UsePermission';
import UserRoles from './UserRoles';
const validations = {
userName: Yup.string().required('AbpAccount::ThisFieldIsRequired.'),
email: Yup.string()
.email('AbpAccount::ThisFieldIsNotAValidEmailAddress.')
.required('AbpAccount::ThisFieldIsRequired.'),
};
let roleNames = [];
function onChangeRoles(roles) {
roleNames = roles;
}
function CreateUpdateUserForm({ editingUser = {}, submit, remove }) {
const [selectedTab, setSelectedTab] = useState(0);
const [showPassword, setShowPassword] = useState(false);
const usernameRef = useRef();
const nameRef = useRef();
const surnameRef = useRef();
const emailRef = useRef();
const phoneNumberRef = useRef();
const passwordRef = useRef();
const hasRemovePermission = usePermission('AbpIdentity.Users.Delete');
const onSubmit = values => {
submit({
...editingUser,
...values,
roleNames,
});
};
const passwordValidation = Yup.lazy(() => {
if (editingUser.id) {
return Yup.string();
}
return Yup.string().required('AbpAccount::ThisFieldIsRequired.');
});
return (
<Formik
enableReinitialize
validationSchema={Yup.object().shape({
...validations,
password: passwordValidation,
})}
initialValues={{
lockoutEnabled: false,
twoFactorEnabled: false,
...editingUser,
}}
onSubmit={values => onSubmit(values)}>
{({ handleChange, handleBlur, handleSubmit, values, errors, isValid, setFieldValue }) => (
<>
<Segment>
<Button first primary={selectedTab === 0} onPress={() => setSelectedTab(0)}>
<Text dark light={selectedTab === 0}>
{i18n.t('AbpIdentity::UserInformations')}
</Text>
</Button>
<Button last primary={selectedTab === 1} onPress={() => setSelectedTab(1)}>
<Text dark light={selectedTab === 1}>
{i18n.t('AbpIdentity::Roles')}
</Text>
</Button>
</Segment>
<Container style={styles.container}>
<Content px20>
<View style={{ display: selectedTab === 0 ? 'flex' : 'none' }}>
<View style={{ alignItems: 'center', margin: 10, zIndex: 1 }} />
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpIdentity::UserName')}*</Label>
<Input
abpInput
ref={usernameRef}
onSubmitEditing={() => nameRef.current._root.focus()}
returnKeyType="next"
onChangeText={handleChange('userName')}
onBlur={handleBlur('userName')}
value={values.userName}
/>
</InputGroup>
<ValidationMessage>{errors.userName}</ValidationMessage>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpIdentity::DisplayName:Name')}</Label>
<Input
abpInput
ref={nameRef}
onSubmitEditing={() => surnameRef.current._root.focus()}
returnKeyType="next"
onChangeText={handleChange('name')}
onBlur={handleBlur('name')}
value={values.name}
/>
</InputGroup>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpIdentity::DisplayName:Surname')}</Label>
<Input
abpInput
ref={surnameRef}
onSubmitEditing={() => emailRef.current._root.focus()}
returnKeyType="next"
onChangeText={handleChange('surname')}
onBlur={handleBlur('surname')}
value={values.surname}
/>
</InputGroup>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpIdentity::EmailAddress')}*</Label>
<Input
abpInput
ref={emailRef}
onSubmitEditing={() => phoneNumberRef.current._root.focus()}
returnKeyType="next"
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
value={values.email}
/>
</InputGroup>
<ValidationMessage>{errors.email}</ValidationMessage>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpIdentity::PhoneNumber')}</Label>
<Input
abpInput
ref={phoneNumberRef}
returnKeyType={!editingUser.id ? 'next' : 'default'}
onChangeText={handleChange('phoneNumber')}
onBlur={handleBlur('phoneNumber')}
value={values.phoneNumber}
/>
</InputGroup>
{!editingUser.id ? (
<>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpIdentity::Password')}*</Label>
<Item abpInput>
<Input
ref={passwordRef}
secureTextEntry={!showPassword}
onChangeText={handleChange('password')}
onBlur={handleBlur('password')}
value={values.password}
/>
<Icon
name={showPassword ? 'eye-off' : 'eye'}
onPress={() => setShowPassword(!showPassword)}
/>
</Item>
</InputGroup>
<ValidationMessage>{errors.password}</ValidationMessage>
</>
) : null}
<ListItem>
<CheckBox
checked={values.lockoutEnabled}
onPress={() => setFieldValue('lockoutEnabled', !values.lockoutEnabled)}
/>
<Body>
<TouchableOpacity
onPress={() => setFieldValue('lockoutEnabled', !values.lockoutEnabled)}>
<Text>{i18n.t('AbpIdentity::DisplayName:LockoutEnabled')}</Text>
</TouchableOpacity>
</Body>
</ListItem>
<ListItem>
<CheckBox
checked={values.twoFactorEnabled}
onPress={() => setFieldValue('twoFactorEnabled', !values.twoFactorEnabled)}
/>
<Body>
<TouchableOpacity
onPress={() => setFieldValue('twoFactorEnabled', !values.twoFactorEnabled)}>
<Text>{i18n.t('AbpIdentity::DisplayName:TwoFactorEnabled')}</Text>
</TouchableOpacity>
</Body>
</ListItem>
</View>
<View
style={{
display: selectedTab === 1 ? 'flex' : 'none',
flex: 1,
}}>
<UserRoles {...{ editingUser, onChangeRoles }} />
</View>
</Content>
</Container>
<FormButtons
submit={handleSubmit}
remove={remove}
removeMessage={i18n.t('AbpIdentity::UserDeletionConfirmationMessage', {
0: editingUser.userName,
})}
isSubmitDisabled={!isValid}
isShowRemove={!!editingUser.id && hasRemovePermission}
/>
</>
)}
</Formik>
);
}
CreateUpdateUserForm.propTypes = {
editingUser: PropTypes.object,
submit: PropTypes.func.isRequired,
remove: PropTypes.func.isRequired,
};
const styles = StyleSheet.create({
container: {
marginBottom: 50,
},
});
export default CreateUpdateUserForm;

69
templates/app/react-native/src/screens/CreateUpdateUser/CreateUpdateUserScreen.js

@ -0,0 +1,69 @@
import PropTypes from 'prop-types';
import React, { useEffect, useState } from 'react';
import { createUser, getUserById, removeUser, updateUser } from '../../api/IdentityAPI';
import LoadingActions from '../../store/actions/LoadingActions';
import { createLoadingSelector } from '../../store/selectors/LoadingSelectors';
import { connectToRedux } from '../../utils/ReduxConnect';
import CreateUpdateUserForm from './CreateUpdateUserForm';
function CreateUpdateUserScreen({ navigation, route, startLoading, stopLoading }) {
const [user, setUser] = useState();
const userId = route.params?.userId;
const remove = () => {
startLoading({ key: 'remove user' });
removeUser(userId)
.then(() => navigation.goBack())
.finally(() => stopLoading({ key: 'remove user' }));
};
useEffect(() => {
if (userId) {
getUserById(userId).then((data = {}) => setUser(data));
}
}, []);
const submit = data => {
startLoading({ key: 'saveUser' });
let request;
if (data.id) {
request = updateUser(data, userId);
} else {
request = createUser(data);
}
request
.then(() => {
navigation.goBack();
})
.finally(() => stopLoading({ key: 'saveUser' }));
};
const renderForm = () => (
<CreateUpdateUserForm editingUser={user} submit={submit} remove={remove} />
);
if (userId && user) {
return renderForm();
}
if (!userId) {
return renderForm();
}
return null;
}
CreateUpdateUserScreen.propTypes = {
startLoading: PropTypes.func.isRequired,
stopLoading: PropTypes.func.isRequired,
};
export default connectToRedux({
component: CreateUpdateUserScreen,
stateProps: state => ({ loading: createLoadingSelector()(state) }),
dispatchProps: {
startLoading: LoadingActions.start,
stopLoading: LoadingActions.stop,
},
});

60
templates/app/react-native/src/screens/CreateUpdateUser/UserRoles.js

@ -0,0 +1,60 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { List, ListItem, CheckBox, Body, Text } from 'native-base';
import { TouchableOpacity } from 'react-native';
import { getAllRoles, getUserRoles } from '../../api/IdentityAPI';
function UserRoles({ editingUser = {}, onChangeRoles }) {
const [roles, setRoles] = useState([]);
const onPress = index => {
setRoles(
roles.map((role, i) => ({
...role,
isSelected: index === i ? !role.isSelected : role.isSelected,
})),
);
};
useEffect(() => {
const requests = [getAllRoles()];
if (editingUser.id) requests.push(getUserRoles(editingUser.id));
Promise.all(requests).then(([allRoles = [], userRoles = []]) => {
setRoles(
allRoles.map(role => ({
...role,
isSelected: editingUser.id
? !!userRoles?.find(userRole => userRole?.id === role?.id)
: role.isDefault,
})),
);
});
}, []);
useEffect(() => {
onChangeRoles(roles.filter(role => role.isSelected).map(role => role.name));
}, [roles]);
return (
<List>
{roles.map((role, index) => (
<ListItem key={role.id}>
<CheckBox checked={role.isSelected} onPress={() => onPress(index)} />
<Body>
<TouchableOpacity onPress={() => onPress(index)}>
<Text>{role.name}</Text>
</TouchableOpacity>
</Body>
</ListItem>
))}
</List>
);
}
UserRoles.propTypes = {
editingUser: PropTypes.objectOf(PropTypes.any).isRequired,
onChangeRoles: PropTypes.func.isRequired,
};
export default UserRoles;

34
templates/app/react-native/src/screens/Home/HomeScreen.js

@ -0,0 +1,34 @@
import i18n from 'i18n-js';
import { Text } from 'native-base';
import React from 'react';
import { StyleSheet, View } from 'react-native';
function HomeScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}> {i18n.t('::Welcome')}</Text>
<Text style={styles.centeredText}> {i18n.t('::LongWelcomeMessage')}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 20,
backgroundColor: '#fff',
},
centeredText: {
textAlign: 'center',
},
title: {
marginBottom: 30,
fontSize: 32,
fontWeight: '300',
textAlign: 'center',
},
});
export default HomeScreen;

128
templates/app/react-native/src/screens/Login/LoginScreen.js

@ -0,0 +1,128 @@
import { Formik } from 'formik';
import i18n from 'i18n-js';
import {
Button,
Container,
Content,
Form,
Input,
InputGroup,
Item,
Label,
Text,
Icon,
} from 'native-base';
import PropTypes from 'prop-types';
import React, { useState } from 'react';
import { View } from 'react-native';
import * as Yup from 'yup';
import { login } from '../../api/AccountAPI';
import TenantBox from '../../components/TenantBox/TenantBox';
import ValidationMessage from '../../components/ValidationMessage/ValidationMessage';
import AppActions from '../../store/actions/AppActions';
import LoadingActions from '../../store/actions/LoadingActions';
import PersistentStorageActions from '../../store/actions/PersistentStorageActions';
import { connectToRedux } from '../../utils/ReduxConnect';
const ValidationSchema = Yup.object().shape({
username: Yup.string().required('AbpAccount::ThisFieldIsRequired.'),
password: Yup.string().required('AbpAccount::ThisFieldIsRequired.'),
});
function LoginScreen({ startLoading, stopLoading, setToken, fetchAppConfig }) {
const [showTenantSelection, setShowTenantSelection] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const toggleTenantSelection = () => {
setShowTenantSelection(!showTenantSelection);
};
const submit = ({ username, password }) => {
startLoading({ key: 'login' });
login({ username, password })
.then(data =>
setToken({
...data,
expire_time: new Date().valueOf() + data.expires_in,
scope: undefined,
}),
)
.then(
() =>
new Promise(resolve =>
fetchAppConfig({ showLoading: false, callback: () => resolve(true) }),
),
)
.finally(() => stopLoading({ key: 'login' }));
};
return (
<Container>
<TenantBox
showTenantSelection={showTenantSelection}
toggleTenantSelection={toggleTenantSelection}
/>
{!showTenantSelection ? (
<Content px20 style={{ flex: 1 }}>
<Formik
validationSchema={ValidationSchema}
initialValues={{ username: '', password: '' }}
onSubmit={submit}>
{({ handleChange, handleBlur, handleSubmit, values, errors }) => (
<Form>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpAccount::UserNameOrEmailAddress')}</Label>
<Input
abpInput
onChangeText={handleChange('username')}
onBlur={handleBlur('username')}
value={values.username}
/>
</InputGroup>
<ValidationMessage>{errors.username}</ValidationMessage>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpAccount::Password')}</Label>
<Item abpInput>
<Input
secureTextEntry={!showPassword}
onChangeText={handleChange('password')}
onBlur={handleBlur('password')}
value={values.password}
/>
<Icon
name={showPassword ? 'eye-off' : 'eye'}
onPress={() => setShowPassword(!showPassword)}
/>
</Item>
</InputGroup>
<ValidationMessage>{errors.password}</ValidationMessage>
<View style={{ marginTop: 20, alignItems: 'center' }}>
<Button abpButton onPress={handleSubmit}>
<Text>{i18n.t('AbpAccount::Login')}</Text>
</Button>
</View>
</Form>
)}
</Formik>
</Content>
) : null}
</Container>
);
}
LoginScreen.propTypes = {
startLoading: PropTypes.func.isRequired,
stopLoading: PropTypes.func.isRequired,
setToken: PropTypes.func.isRequired,
fetchAppConfig: PropTypes.func.isRequired,
};
export default connectToRedux({
component: LoginScreen,
dispatchProps: {
startLoading: LoadingActions.start,
stopLoading: LoadingActions.stop,
fetchAppConfig: AppActions.fetchAppConfigAsync,
setToken: PersistentStorageActions.setToken,
},
});

122
templates/app/react-native/src/screens/ManageProfile/ManageProfileForm.js

@ -0,0 +1,122 @@
import { Formik } from 'formik';
import i18n from 'i18n-js';
import { Container, Content, Form, Input, InputGroup, Label } from 'native-base';
import PropTypes from 'prop-types';
import React, { useRef } from 'react';
import * as Yup from 'yup';
import FormButtons from '../../components/FormButtons/FormButtons';
import ValidationMessage from '../../components/ValidationMessage/ValidationMessage';
const ValidationSchema = Yup.object().shape({
userName: Yup.string().required('AbpAccount::ThisFieldIsRequired.'),
email: Yup.string()
.required('AbpAccount::ThisFieldIsRequired.')
.email('AbpAccount::ThisFieldIsNotAValidEmailAddress.'),
});
function ManageProfileForm({ editingUser = {}, submit, cancel }) {
const usernameRef = useRef();
const nameRef = useRef();
const surnameRef = useRef();
const emailRef = useRef();
const phoneNumberRef = useRef();
const onSubmit = values => {
submit({
...editingUser,
...values,
});
};
return (
<Formik
enableReinitialize
validationSchema={ValidationSchema}
initialValues={{
...editingUser,
}}
onSubmit={values => onSubmit(values)}>
{({ handleChange, handleBlur, handleSubmit, values, errors, isValid }) => (
<>
<Container>
<Content px20>
<Form>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpIdentity::UserName')}*</Label>
<Input
ref={usernameRef}
onSubmitEditing={() => nameRef.current._root.focus()}
returnKeyType="next"
onChangeText={handleChange('userName')}
onBlur={handleBlur('userName')}
value={values.userName}
abpInput
/>
</InputGroup>
<ValidationMessage>{errors.userName}</ValidationMessage>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpIdentity::DisplayName:Name')}</Label>
<Input
abpInput
ref={nameRef}
onSubmitEditing={() => surnameRef.current._root.focus()}
returnKeyType="next"
onChangeText={handleChange('name')}
onBlur={handleBlur('name')}
value={values.name}
/>
</InputGroup>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpIdentity::DisplayName:Surname')}</Label>
<Input
abpInput
ref={surnameRef}
onSubmitEditing={() => phoneNumberRef.current._root.focus()}
returnKeyType="next"
onChangeText={handleChange('surname')}
onBlur={handleBlur('surname')}
value={values.surname}
/>
</InputGroup>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpIdentity::PhoneNumber')}</Label>
<Input
abpInput
ref={phoneNumberRef}
onSubmitEditing={() => emailRef.current._root.focus()}
returnKeyType="next"
onChangeText={handleChange('phoneNumber')}
onBlur={handleBlur('phoneNumber')}
value={values.phoneNumber}
/>
</InputGroup>
<InputGroup abpInputGroup>
<Label abpLabel>{i18n.t('AbpIdentity::EmailAddress')}*</Label>
<Input
abpInput
ref={emailRef}
returnKeyType="done"
onSubmitEditing={handleSubmit}
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
value={values.email}
/>
</InputGroup>
<ValidationMessage>{errors.email}</ValidationMessage>
</Form>
</Content>
</Container>
<FormButtons submit={handleSubmit} cancel={cancel} isSubmitDisabled={!isValid} />
</>
)}
</Formik>
);
}
ManageProfileForm.propTypes = {
editingUser: PropTypes.object.isRequired,
submit: PropTypes.func.isRequired,
cancel: PropTypes.func.isRequired,
};
export default ManageProfileForm;

50
templates/app/react-native/src/screens/ManageProfile/ManageProfileScreen.js

@ -0,0 +1,50 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { updateProfileDetail, getProfileDetail } from '../../api/IdentityAPI';
import ManageProfileForm from './ManageProfileForm';
import LoadingActions from '../../store/actions/LoadingActions';
import { connectToRedux } from '../../utils/ReduxConnect';
function ManageProfileScreen({ navigation, startLoading, stopLoading }) {
const [user, setUser] = useState();
useEffect(() => {
if (!user) {
startLoading({ key: 'manageProfile' });
getProfileDetail()
.then((data = {}) => setUser(data))
.finally(() => stopLoading({ key: 'manageProfile' }));
}
});
const submit = data => {
startLoading({ key: 'manageProfile' });
updateProfileDetail(data)
.then(() => {
navigation.goBack();
})
.finally(() => stopLoading({ key: 'manageProfile' }));
};
return (
<>
{user ? (
<ManageProfileForm editingUser={user} submit={submit} cancel={() => navigation.goBack()} />
) : null}
</>
);
}
ManageProfileScreen.propTypes = {
startLoading: PropTypes.func.isRequired,
stopLoading: PropTypes.func.isRequired,
};
export default connectToRedux({
component: ManageProfileScreen,
dispatchProps: {
startLoading: LoadingActions.start,
stopLoading: LoadingActions.stop,
},
});

145
templates/app/react-native/src/screens/Settings/SettingsScreen.js

@ -0,0 +1,145 @@
import i18n from 'i18n-js';
import {
Body,
Button,
Icon,
Label,
Left,
List,
ListItem,
Picker,
Right,
Text,
Thumbnail,
} from 'native-base';
import PropTypes from 'prop-types';
import React, { useCallback, useState } from 'react';
import { View } from 'react-native';
import { useFocusEffect } from '@react-navigation/native';
import { getProfileDetail } from '../../api/IdentityAPI';
import AppActions from '../../store/actions/AppActions';
import {
createLanguageSelector,
createLanguagesSelector,
} from '../../store/selectors/AppSelectors';
import { connectToRedux } from '../../utils/ReduxConnect';
import { createTenantSelector } from '../../store/selectors/PersistentStorageSelectors';
function SettingsScreen({
navigation,
language,
languages,
setLanguageAsync,
logoutAsync,
tenant = {},
}) {
const [user, setUser] = useState({});
const fetchUser = () => {
getProfileDetail().then(data => {
setUser(data || {});
});
};
useFocusEffect(
useCallback(() => {
fetchUser();
}, []),
);
return (
<View>
<List>
<ListItem itemDivider />
<ListItem
noIndent
style={{ backgroundColor: '#fff' }}
onPress={() => navigation.navigate('ManageProfile')}>
<Left style={{ alignItems: 'center' }}>
<Thumbnail source={require('../../../assets/avatar.png')} />
<Body>
<Text>
{tenant.name ? `${tenant.name} / ` : ''}
{user.userName ? `${user.userName}` : ''}
</Text>
<Text note>{user.email}</Text>
</Body>
</Left>
<Right>
<Icon active name="arrow-forward" />
</Right>
</ListItem>
<ListItem itemDivider />
<ListItem
noIndent
icon
style={{ backgroundColor: '#fff' }}
onPress={() => navigation.navigate('ChangePassword')}>
<Body>
<Text>{i18n.t('AbpUi::ChangePassword')}</Text>
</Body>
<Right>
<Icon active name="arrow-forward" />
</Right>
</ListItem>
<ListItem itemDivider />
<ListItem itemDivider>
<Label abpLabel style={{ marginBottom: 0 }}>
{i18n.t('AbpUi::Language')}
</Label>
</ListItem>
<ListItem noIndent icon style={{ backgroundColor: '#fff' }}>
<Body>
<Picker
mode="dropdown"
iosHeader={i18n.t('AbpUi::Language')}
iosIcon={<Icon active name="arrow-down" />}
onValueChange={value => setLanguageAsync(value)}
selectedValue={language.cultureName}
textStyle={{ paddingLeft: 0 }}>
{languages.map(lang => (
<Picker.Item
label={lang.displayName}
value={lang.cultureName}
key={lang.cultureName}
/>
))}
</Picker>
</Body>
</ListItem>
<ListItem itemDivider />
<Button
abpButton
danger
style={{ borderRadius: 0 }}
onPress={() => {
logoutAsync();
}}>
<Text>{i18n.t('AbpAccount::Logout')}</Text>
</Button>
</List>
</View>
);
}
SettingsScreen.propTypes = {
setLanguageAsync: PropTypes.func.isRequired,
logoutAsync: PropTypes.func.isRequired,
language: PropTypes.object.isRequired,
languages: PropTypes.array.isRequired,
tenant: PropTypes.object.isRequired,
};
export default connectToRedux({
component: SettingsScreen,
stateProps: state => ({
languages: createLanguagesSelector()(state),
language: createLanguageSelector()(state),
tenant: createTenantSelector()(state),
}),
dispatchProps: {
setLanguageAsync: AppActions.setLanguageAsync,
logoutAsync: AppActions.logoutAsync,
},
});

59
templates/app/react-native/src/screens/Tenants/TenantsScreen.js

@ -0,0 +1,59 @@
import React from 'react';
import i18n from 'i18n-js';
import { ListItem, Text, Icon, Left, Body, Fab } from 'native-base';
import { StyleSheet } from 'react-native';
import { getTenants } from '../../api/TenantManagementAPI';
import { activeTheme } from '../../theme/variables';
import DataList from '../../components/DataList/DataList';
import { withPermission } from '../../hocs/PermissionHOC';
const CreateTenantButtonWithPermission = withPermission(Fab, 'AbpTenantManagement.Tenants.Create');
function TenantsScreen({ navigation }) {
return (
<>
<DataList
navigation={navigation}
fetchFn={getTenants}
render={tenant => (
<ListItem avatar onPress={() => navigateToCreateUpdateTenantScreen(navigation, tenant)}>
<Left style={styles.listItem}>
<Body>
<Text>{tenant.name}</Text>
</Body>
</Left>
</ListItem>
)}
/>
<CreateTenantButtonWithPermission
style={{ backgroundColor: activeTheme.brandPrimary }}
position="bottomRight"
onPress={() => navigation.navigate('CreateUpdateTenant')}>
<Icon name="add" />
</CreateTenantButtonWithPermission>
</>
);
}
const navigateToCreateUpdateTenantScreen = (navigation, tenant = {}) => {
navigation.navigate('CreateUpdateTenant', {
tenantId: tenant.id,
});
};
const styles = StyleSheet.create({
listItem: {
alignItems: 'center',
marginVertical: 3,
marginLeft: -15,
paddingTop: 5,
paddingBottom: 5,
paddingLeft: 15,
backgroundColor: '#f9f9f9',
width: '110%',
minHeight: 50,
},
});
export default TenantsScreen;

61
templates/app/react-native/src/screens/Users/UsersScreen.js

@ -0,0 +1,61 @@
import React from 'react';
import { ListItem, Text, Icon, Left, Thumbnail, Body, Fab } from 'native-base';
import { StyleSheet } from 'react-native';
import { getUsers } from '../../api/IdentityAPI';
import { activeTheme } from '../../theme/variables';
import DataList from '../../components/DataList/DataList';
import { withPermission } from '../../hocs/PermissionHOC';
const CreateUserButtonWithPermission = withPermission(Fab, 'AbpIdentity.Users.Create');
function UsersScreen({ navigation }) {
return (
<>
<DataList
navigation={navigation}
fetchFn={getUsers}
render={user => (
<ListItem avatar onPress={() => navigateToCreateUpdateUserScreen(navigation, user)}>
<Left style={styles.listItem}>
<Thumbnail source={require('../../../assets/avatar.png')} />
<Body>
<Text>{user.userName}</Text>
<Text note>{user.email}</Text>
</Body>
</Left>
</ListItem>
)}
/>
<CreateUserButtonWithPermission
containerStyle={{}}
style={{ backgroundColor: activeTheme.brandPrimary }}
position="bottomRight"
onPress={() => navigation.navigate('CreateUpdateUser')}>
<Icon name="add" />
</CreateUserButtonWithPermission>
</>
);
}
const navigateToCreateUpdateUserScreen = (navigation, user = {}) => {
navigation.navigate('CreateUpdateUser', {
userId: user.id,
});
};
const styles = StyleSheet.create({
container: { flex: 1 },
listItem: {
alignItems: 'center',
marginVertical: 3,
marginLeft: -15,
paddingTop: 5,
paddingBottom: 5,
paddingLeft: 15,
backgroundColor: '#f9f9f9',
width: '110%',
},
});
export default UsersScreen;

19
templates/app/react-native/src/store/actions/AppActions.js

@ -0,0 +1,19 @@
import { createAction } from '@reduxjs/toolkit';
const fetchAppConfigAsync = createAction(
'app/fetchAppConfigAsync',
({ callback = () => {}, showLoading = true } = {}) => ({ payload: { callback, showLoading } }),
);
const setAppConfig = createAction('app/setAppConfig');
const setLanguageAsync = createAction('app/setLanguageAsync');
const logoutAsync = createAction('app/logoutAsync');
export default {
fetchAppConfigAsync,
setAppConfig,
setLanguageAsync,
logoutAsync,
};

13
templates/app/react-native/src/store/actions/LoadingActions.js

@ -0,0 +1,13 @@
import { createAction } from '@reduxjs/toolkit';
const start = createAction('loading/start');
const stop = createAction('loading/stop');
const clear = createAction('loading/clear');
export default {
start,
stop,
clear,
};

13
templates/app/react-native/src/store/actions/PersistentStorageActions.js

@ -0,0 +1,13 @@
import { createAction } from '@reduxjs/toolkit';
const setToken = createAction('persistentStorage/setToken');
const setLanguage = createAction('persistentStorage/setLanguage');
const setTenant = createAction('persistentStorage/setTenant');
export default {
setToken,
setLanguage,
setTenant,
};

25
templates/app/react-native/src/store/index.js

@ -0,0 +1,25 @@
import { configureStore } from '@reduxjs/toolkit';
import createSagaMiddleware from 'redux-saga';
import { persistStore, persistReducer } from 'redux-persist';
import { AsyncStorage } from 'react-native';
import { rootSaga } from './sagas';
import rootReducer from './reducers';
const sagaMiddleware = createSagaMiddleware();
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: ['persistentStorage'],
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: [sagaMiddleware],
});
export const persistor = persistStore(store);
sagaMiddleware.run(rootSaga);

12
templates/app/react-native/src/store/reducers/AppReducer.js

@ -0,0 +1,12 @@
import { createReducer } from '@reduxjs/toolkit';
import AppActions from '../actions/AppActions';
const initialState = {
appConfig: {},
};
export default createReducer(initialState, builder =>
builder.addCase(AppActions.setAppConfig, (state, action) => {
state.appConfig = action.payload;
}),
);

23
templates/app/react-native/src/store/reducers/LoadingReducer.js

@ -0,0 +1,23 @@
import { createReducer } from '@reduxjs/toolkit';
import LoadingActions from '../actions/LoadingActions';
const initialState = { activeLoadings: {}, loading: false };
export default createReducer(initialState, builder =>
builder
.addCase(LoadingActions.start, (state, action) => {
const { key, opacity } = action.payload;
return {
...state,
actives: { ...state.activeLoadings, [key]: action },
loading: true,
opacity,
};
})
.addCase(LoadingActions.stop, (state, action) => {
delete state.activeLoadings[action.payload.key];
state.loading = !!Object.keys(state.activeLoadings).length;
})
.addCase(LoadingActions.clear, () => ({})),
);

17
templates/app/react-native/src/store/reducers/PersistentStorageReducer.js

@ -0,0 +1,17 @@
import { createReducer } from '@reduxjs/toolkit';
import PersistentStorageActions from '../actions/PersistentStorageActions';
const initialState = { token: {}, language: null, tenant: {} };
export default createReducer(initialState, builder =>
builder
.addCase(PersistentStorageActions.setToken, (state, action) => {
state.token = action.payload;
})
.addCase(PersistentStorageActions.setLanguage, (state, action) => {
state.language = action.payload;
})
.addCase(PersistentStorageActions.setTenant, (state, action) => {
state.tenant = action.payload;
}),
);

12
templates/app/react-native/src/store/reducers/index.js

@ -0,0 +1,12 @@
import { combineReducers } from '@reduxjs/toolkit';
import LoadingReducer from './LoadingReducer';
import AppReducer from './AppReducer';
import PersistentStorageReducer from './PersistentStorageReducer';
const rootReducer = combineReducers({
loading: LoadingReducer,
app: AppReducer,
persistentStorage: PersistentStorageReducer,
});
export default rootReducer;

34
templates/app/react-native/src/store/sagas/AppSaga.js

@ -0,0 +1,34 @@
import { call, put, takeLatest, all } from 'redux-saga/effects';
import { getApplicationConfiguration } from '../../api/ApplicationConfigurationAPI';
import { logout as logoutAsync } from '../../api/AccountAPI';
import AppActions from '../actions/AppActions';
import LoadingActions from '../actions/LoadingActions';
import PersistentStorageActions from '../actions/PersistentStorageActions';
function* fetchAppConfig({ payload: { showLoading, callback } }) {
if (showLoading) yield put(LoadingActions.start({ key: 'appConfig', opacity: 1 }));
const data = yield call(getApplicationConfiguration);
yield put(AppActions.setAppConfig(data));
yield put(PersistentStorageActions.setLanguage(data.localization.currentCulture.cultureName));
if (showLoading) yield put(LoadingActions.stop({ key: 'appConfig' }));
callback();
}
function* setLanguage(action) {
yield put(PersistentStorageActions.setLanguage(action.payload));
yield put(AppActions.fetchAppConfigAsync());
}
function* logout() {
yield call(logoutAsync);
yield put(PersistentStorageActions.setToken({}));
yield put(AppActions.fetchAppConfigAsync());
}
export default function*() {
yield all([
takeLatest(AppActions.setLanguageAsync.type, setLanguage),
takeLatest(AppActions.fetchAppConfigAsync.type, fetchAppConfig),
takeLatest(AppActions.logoutAsync.type, logout),
]);
}

6
templates/app/react-native/src/store/sagas/index.js

@ -0,0 +1,6 @@
import { all, fork } from 'redux-saga/effects';
import AppSaga from './AppSaga';
export function* rootSaga() {
yield all([fork(AppSaga)]);
}

19
templates/app/react-native/src/store/selectors/AppSelectors.js

@ -0,0 +1,19 @@
import { createSelector } from 'reselect';
const getApp = state => state.app;
export function createAppConfigSelector() {
return createSelector([getApp], state => state.appConfig);
}
export function createLanguageSelector() {
return createSelector([getApp], state => state?.appConfig?.localization?.currentCulture);
}
export function createLanguagesSelector() {
return createSelector([getApp], state => state?.appConfig?.localization?.languages);
}
export function createGrantedPolicySelector(key) {
return createSelector([getApp], state => state?.appConfig?.auth?.grantedPolicies[key] ?? false);
}

11
templates/app/react-native/src/store/selectors/LoadingSelectors.js

@ -0,0 +1,11 @@
import { createSelector } from 'reselect';
const getLoading = state => state.loading;
export function createLoadingSelector() {
return createSelector([getLoading], loading => loading.loading);
}
export function createOpacitySelector() {
return createSelector([getLoading], loading => loading.opacity);
}

11
templates/app/react-native/src/store/selectors/PersistentStorageSelectors.js

@ -0,0 +1,11 @@
import { createSelector } from 'reselect';
const getPersistentStorage = state => state.persistentStorage;
export function createTokenSelector() {
return createSelector([getPersistentStorage], persistentStorage => persistentStorage.token);
}
export function createTenantSelector() {
return createSelector([getPersistentStorage], persistentStorage => persistentStorage.tenant);
}

39
templates/app/react-native/src/theme/components/Badge.js

@ -0,0 +1,39 @@
// @flow
import variable from '../variables/Platform';
import { PLATFORM } from '../variables/CommonColor';
export default (variables /* : * */ = variable) => {
const badgeTheme = {
'.primary': {
backgroundColor: variables.buttonPrimaryBg,
},
'.warning': {
backgroundColor: variables.buttonWarningBg,
},
'.info': {
backgroundColor: variables.buttonInfoBg,
},
'.success': {
backgroundColor: variables.buttonSuccessBg,
},
'.danger': {
backgroundColor: variables.buttonDangerBg,
},
'NativeBase.Text': {
color: variables.badgeColor,
fontSize: variables.fontSizeBase,
lineHeight: variables.lineHeight - 1,
textAlign: 'center',
paddingHorizontal: 3,
},
backgroundColor: variables.badgeBg,
padding: variables.badgePadding,
paddingHorizontal: 6,
alignSelf: 'flex-start',
justifyContent: variables.platform === PLATFORM.IOS ? 'center' : undefined,
borderRadius: 13.5,
height: 27,
};
return badgeTheme;
};

11
templates/app/react-native/src/theme/components/Body.js

@ -0,0 +1,11 @@
// @flow
export default () => {
const bodyTheme = {
flex: 1,
alignItems: 'center',
alignSelf: 'center',
};
return bodyTheme;
};

384
templates/app/react-native/src/theme/components/Button.js

@ -0,0 +1,384 @@
// @flow
import variable from '../variables/Platform';
import { PLATFORM } from '../variables/CommonColor';
export default (variables /* : * */ = variable) => {
const { platformStyle } = variables;
const { platform } = variables;
const darkCommon = {
'NativeBase.Text': {
color: variables.brandDark,
},
'NativeBase.Icon': {
color: variables.brandDark,
},
'NativeBase.IconNB': {
color: variables.brandDark,
},
};
const lightCommon = {
'NativeBase.Text': {
color: variables.brandLight,
},
'NativeBase.Icon': {
color: variables.brandLight,
},
'NativeBase.IconNB': {
color: variables.brandLight,
},
};
const primaryCommon = {
'NativeBase.Text': {
color: variables.buttonPrimaryBg,
},
'NativeBase.Icon': {
color: variables.buttonPrimaryBg,
},
'NativeBase.IconNB': {
color: variables.buttonPrimaryBg,
},
};
const successCommon = {
'NativeBase.Text': {
color: variables.buttonSuccessBg,
},
'NativeBase.Icon': {
color: variables.buttonSuccessBg,
},
'NativeBase.IconNB': {
color: variables.buttonSuccessBg,
},
};
const infoCommon = {
'NativeBase.Text': {
color: variables.buttonInfoBg,
},
'NativeBase.Icon': {
color: variables.buttonInfoBg,
},
'NativeBase.IconNB': {
color: variables.buttonInfoBg,
},
};
const warningCommon = {
'NativeBase.Text': {
color: variables.buttonWarningBg,
},
'NativeBase.Icon': {
color: variables.buttonWarningBg,
},
'NativeBase.IconNB': {
color: variables.buttonWarningBg,
},
};
const dangerCommon = {
'NativeBase.Text': {
color: variables.buttonDangerBg,
},
'NativeBase.Icon': {
color: variables.buttonDangerBg,
},
'NativeBase.IconNB': {
color: variables.buttonDangerBg,
},
};
const buttonTheme = {
'.disabled': {
'.transparent': {
backgroundColor: 'transparent',
'NativeBase.Text': {
color: variables.buttonDisabledBg,
},
'NativeBase.Icon': {
color: variables.buttonDisabledBg,
},
'NativeBase.IconNB': {
color: variables.buttonDisabledBg,
},
},
'NativeBase.Icon': {
color: variables.brandLight,
},
'NativeBase.IconNB': {
color: variables.brandLight,
},
backgroundColor: variables.buttonDisabledBg,
},
'.bordered': {
'.dark': {
...darkCommon,
backgroundColor: 'transparent',
borderColor: variables.brandDark,
borderWidth: variables.borderWidth * 2,
},
'.light': {
...lightCommon,
backgroundColor: 'transparent',
borderColor: variables.brandLight,
borderWidth: variables.borderWidth * 2,
},
'.primary': {
...primaryCommon,
backgroundColor: 'transparent',
borderColor: variables.buttonPrimaryBg,
borderWidth: variables.borderWidth * 2,
},
'.success': {
...successCommon,
backgroundColor: 'transparent',
borderColor: variables.buttonSuccessBg,
borderWidth: variables.borderWidth * 2,
},
'.info': {
...infoCommon,
backgroundColor: 'transparent',
borderColor: variables.buttonInfoBg,
borderWidth: variables.borderWidth * 2,
},
'.warning': {
...warningCommon,
backgroundColor: 'transparent',
borderColor: variables.buttonWarningBg,
borderWidth: variables.borderWidth * 2,
},
'.danger': {
...dangerCommon,
backgroundColor: 'transparent',
borderColor: variables.buttonDangerBg,
borderWidth: variables.borderWidth * 2,
},
'.disabled': {
backgroundColor: 'transparent',
borderColor: variables.buttonDisabledBg,
borderWidth: variables.borderWidth * 2,
'NativeBase.Text': {
color: variables.buttonDisabledBg,
},
},
...primaryCommon,
borderWidth: variables.borderWidth * 2,
elevation: null,
shadowColor: null,
shadowOffset: null,
shadowOpacity: null,
shadowRadius: null,
backgroundColor: 'transparent',
},
'.dark': {
'.bordered': {
...darkCommon,
},
backgroundColor: variables.brandDark,
},
'.light': {
'.transparent': {
...lightCommon,
backgroundColor: 'transparent',
},
'.bordered': {
...lightCommon,
},
...darkCommon,
backgroundColor: variables.brandLight,
},
'.primary': {
'.bordered': {
...primaryCommon,
},
backgroundColor: variables.buttonPrimaryBg,
},
'.success': {
'.bordered': {
...successCommon,
},
backgroundColor: variables.buttonSuccessBg,
},
'.info': {
'.bordered': {
...infoCommon,
},
backgroundColor: variables.buttonInfoBg,
},
'.warning': {
'.bordered': {
...warningCommon,
},
backgroundColor: variables.buttonWarningBg,
},
'.danger': {
'.bordered': {
...dangerCommon,
},
backgroundColor: variables.buttonDangerBg,
},
'.block': {
justifyContent: 'center',
alignSelf: 'stretch',
},
'.full': {
justifyContent: 'center',
alignSelf: 'stretch',
borderRadius: 0,
},
'.rounded': {
borderRadius: variables.borderRadiusLarge,
},
'.transparent': {
backgroundColor: 'transparent',
elevation: 0,
shadowColor: null,
shadowOffset: null,
shadowRadius: null,
shadowOpacity: null,
...primaryCommon,
'.dark': {
...darkCommon,
},
'.danger': {
...dangerCommon,
},
'.warning': {
...warningCommon,
},
'.info': {
...infoCommon,
},
'.primary': {
...primaryCommon,
},
'.success': {
...successCommon,
},
'.light': {
...lightCommon,
},
'.disabled': {
backgroundColor: 'transparent',
borderColor: variables.buttonDisabledBg,
borderWidth: variables.borderWidth * 2,
'NativeBase.Text': {
color: variables.buttonDisabledBg,
},
'NativeBase.Icon': {
color: variables.buttonDisabledBg,
},
'NativeBase.IconNB': {
color: variables.buttonDisabledBg,
},
},
},
'.small': {
height: 30,
'NativeBase.Text': {
fontSize: 14,
},
'NativeBase.Icon': {
fontSize: 20,
paddingTop: 0,
},
'NativeBase.IconNB': {
fontSize: 20,
paddingTop: 0,
},
},
'.large': {
height: 60,
'NativeBase.Text': {
fontSize: 22,
},
},
'.capitalize': {},
'.vertical': {
flexDirection: 'column',
height: null,
},
'NativeBase.Text': {
fontFamily: variables.buttonFontFamily,
marginLeft: 0,
marginRight: 0,
color: variables.inverseTextColor,
fontSize: variables.buttonTextSize,
paddingHorizontal: 16,
backgroundColor: 'transparent',
},
'NativeBase.Icon': {
color: variables.inverseTextColor,
fontSize: 24,
marginHorizontal: 16,
paddingTop: platform === PLATFORM.IOS ? 2 : undefined,
},
'NativeBase.IconNB': {
color: variables.inverseTextColor,
fontSize: 24,
marginHorizontal: 16,
paddingTop: platform === PLATFORM.IOS ? 2 : undefined,
},
'.iconLeft': {
'NativeBase.Text': {
marginLeft: 0,
},
'NativeBase.IconNB': {
marginRight: 0,
marginLeft: 16,
},
'NativeBase.Icon': {
marginRight: 0,
marginLeft: 16,
},
},
'.iconRight': {
'NativeBase.Text': {
marginRight: 0,
},
'NativeBase.IconNB': {
marginLeft: 0,
marginRight: 16,
},
'NativeBase.Icon': {
marginLeft: 0,
marginRight: 16,
},
},
'.picker': {
'NativeBase.Text': {
'.note': {
fontSize: 16,
lineHeight: null,
},
},
},
paddingVertical: variables.buttonPadding,
backgroundColor: variables.buttonPrimaryBg,
borderRadius: variables.borderRadiusBase,
borderColor: variables.buttonPrimaryBg,
borderWidth: null,
height: 45,
flexDirection: 'row',
elevation: 2,
shadowColor: platformStyle === PLATFORM.MATERIAL ? variables.brandDark : undefined,
shadowOffset: platformStyle === PLATFORM.MATERIAL ? { width: 0, height: 2 } : undefined,
shadowOpacity: platformStyle === PLATFORM.MATERIAL ? 0.2 : undefined,
shadowRadius: platformStyle === PLATFORM.MATERIAL ? 1.2 : undefined,
alignItems: 'center',
justifyContent: 'space-between',
};
return buttonTheme;
};

37
templates/app/react-native/src/theme/components/Card.js

@ -0,0 +1,37 @@
// @flow
import variable from '../variables/Platform';
export default (variables /* : * */ = variable) => {
const cardTheme = {
'.transparent': {
shadowColor: null,
shadowOffset: null,
shadowOpacity: null,
shadowRadius: null,
elevation: null,
backgroundColor: 'transparent',
borderWidth: 0,
},
'.noShadow': {
shadowColor: null,
shadowOffset: null,
shadowOpacity: null,
elevation: null,
},
marginVertical: 5,
marginHorizontal: 2,
borderWidth: variables.borderWidth,
borderRadius: variables.cardBorderRadius,
borderColor: variables.cardBorderColor,
flexWrap: 'nowrap',
backgroundColor: variables.cardDefaultBg,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 1.5,
elevation: 3,
};
return cardTheme;
};

198
templates/app/react-native/src/theme/components/CardItem.js

@ -0,0 +1,198 @@
// @flow
import { StyleSheet } from 'react-native';
import variable from '../variables/Platform';
import { PLATFORM } from '../variables/CommonColor';
export default (variables /* : * */ = variable) => {
const { platform } = variables;
const transparentBtnCommon = {
'NativeBase.Text': {
fontSize: variables.DefaultFontSize - 3,
color: variables.sTabBarActiveTextColor,
},
'NativeBase.Icon': {
fontSize: variables.iconFontSize - 10,
color: variables.sTabBarActiveTextColor,
marginHorizontal: null,
},
'NativeBase.IconNB': {
fontSize: variables.iconFontSize - 10,
color: variables.sTabBarActiveTextColor,
},
paddingVertical: null,
paddingHorizontal: null,
};
const cardItemTheme = {
'NativeBase.Left': {
'NativeBase.Body': {
'NativeBase.Text': {
'.note': {
color: variables.listNoteColor,
fontWeight: '400',
marginRight: 20,
},
},
flex: 1,
marginLeft: 10,
alignItems: null,
},
'NativeBase.Icon': {
fontSize: variables.iconFontSize,
},
'NativeBase.IconNB': {
fontSize: variables.iconFontSize,
},
'NativeBase.Text': {
marginLeft: 10,
alignSelf: 'center',
},
'NativeBase.Button': {
'.transparent': {
...transparentBtnCommon,
paddingRight: variables.cardItemPadding + 5,
},
},
flex: 1,
flexDirection: 'row',
alignItems: 'center',
},
'.content': {
'NativeBase.Text': {
color: platform === PLATFORM.IOS ? '#555' : '#222',
fontSize: variables.DefaultFontSize - 2,
},
},
'.cardBody': {
padding: -5,
'NativeBase.Text': {
marginTop: 5,
},
},
'NativeBase.Body': {
'NativeBase.Text': {
'.note': {
color: variables.listNoteColor,
fontWeight: '200',
marginRight: 20,
},
},
'NativeBase.Button': {
'.transparent': {
...transparentBtnCommon,
paddingRight: variables.cardItemPadding + 5,
alignSelf: 'stretch',
},
},
flex: 1,
alignSelf: 'stretch',
alignItems: 'flex-start',
},
'NativeBase.Right': {
'NativeBase.Badge': {
alignSelf: null,
},
'NativeBase.Button': {
'.transparent': {
...transparentBtnCommon,
},
alignSelf: null,
},
'NativeBase.Icon': {
alignSelf: null,
fontSize: variables.iconFontSize - 8,
color: variables.cardBorderColor,
},
'NativeBase.IconNB': {
alignSelf: null,
fontSize: variables.iconFontSize - 8,
color: variables.cardBorderColor,
},
'NativeBase.Text': {
fontSize: variables.DefaultFontSize - 1,
alignSelf: null,
},
'NativeBase.Thumbnail': {
alignSelf: null,
},
'NativeBase.Image': {
alignSelf: null,
},
'NativeBase.Radio': {
alignSelf: null,
},
'NativeBase.Checkbox': {
alignSelf: null,
},
'NativeBase.Switch': {
alignSelf: null,
},
flex: 0.8,
},
'.header': {
'NativeBase.Text': {
fontSize: 16,
fontWeight: platform === PLATFORM.IOS ? '600' : '500',
},
'.bordered': {
'NativeBase.Text': {
color: variables.brandPrimary,
fontWeight: platform === PLATFORM.IOS ? '600' : '500',
},
borderBottomWidth: variables.borderWidth,
},
borderBottomWidth: null,
paddingVertical: variables.cardItemPadding + 5,
},
'.footer': {
'NativeBase.Text': {
fontSize: 16,
fontWeight: platform === PLATFORM.IOS ? '600' : '500',
},
'.bordered': {
'NativeBase.Text': {
color: variables.brandPrimary,
fontWeight: platform === PLATFORM.IOS ? '600' : '500',
},
borderTopWidth: variables.borderWidth,
},
borderBottomWidth: null,
},
'NativeBase.Text': {
'.note': {
color: variables.listNoteColor,
fontWeight: '200',
},
},
'NativeBase.Icon': {
width: variables.iconFontSize + 5,
fontSize: variables.iconFontSize - 2,
},
'NativeBase.IconNB': {
width: variables.iconFontSize + 5,
fontSize: variables.iconFontSize - 2,
},
'.bordered': {
borderBottomWidth: StyleSheet.hairlineWidth,
borderColor: variables.cardBorderColor,
},
'.first': {
borderTopLeftRadius: variables.cardBorderRadius,
borderTopRightRadius: variables.cardBorderRadius,
},
'.last': {
borderBottomLeftRadius: variables.cardBorderRadius,
borderBottomRightRadius: variables.cardBorderRadius,
},
flexDirection: 'row',
alignItems: 'center',
borderRadius: variables.cardBorderRadius,
padding: variables.cardItemPadding + 5,
paddingVertical: variables.cardItemPadding,
backgroundColor: variables.cardDefaultBg,
};
return cardItemTheme;
};

38
templates/app/react-native/src/theme/components/CheckBox.js

@ -0,0 +1,38 @@
// @flow
import variable from '../variables/Platform';
export default (variables /* : * */ = variable) => {
const checkBoxTheme = {
'.checked': {
'NativeBase.Icon': {
color: variables.checkboxTickColor,
},
'NativeBase.IconNB': {
color: variables.checkboxTickColor,
},
},
'NativeBase.Icon': {
color: 'transparent',
lineHeight: variables.CheckboxIconSize,
marginTop: variables.CheckboxIconMarginTop,
fontSize: variables.CheckboxFontSize,
},
'NativeBase.IconNB': {
color: 'transparent',
lineHeight: variables.CheckboxIconSize,
marginTop: variables.CheckboxIconMarginTop,
fontSize: variables.CheckboxFontSize,
},
borderRadius: variables.CheckboxRadius,
overflow: 'hidden',
width: variables.checkboxSize,
height: variables.checkboxSize,
borderWidth: variables.CheckboxBorderWidth,
paddingLeft: variables.CheckboxPaddingLeft - 1,
paddingBottom: variables.CheckboxPaddingBottom,
left: 10,
};
return checkBoxTheme;
};

17
templates/app/react-native/src/theme/components/Container.js

@ -0,0 +1,17 @@
// @flow
import { Platform, Dimensions } from 'react-native';
import variable from '../variables/Platform';
import { PLATFORM } from '../variables/CommonColor';
const deviceHeight = Dimensions.get('window').height;
export default (variables /* : * */ = variable) => {
const theme = {
flex: 1,
height: Platform.OS === PLATFORM.IOS ? deviceHeight : deviceHeight - 20,
backgroundColor: variables.containerBgColor,
};
return theme;
};

14
templates/app/react-native/src/theme/components/Content.js

@ -0,0 +1,14 @@
// @flow
export default () => {
const contentTheme = {
flex: 1,
backgroundColor: 'transparent',
'NativeBase.Segment': {
borderWidth: 0,
backgroundColor: 'transparent',
},
};
return contentTheme;
};

25
templates/app/react-native/src/theme/components/Fab.js

@ -0,0 +1,25 @@
// @flow
export default () => {
const fabTheme = {
'NativeBase.Button': {
alignItems: 'center',
padding: null,
justifyContent: 'center',
'NativeBase.Icon': {
alignSelf: 'center',
fontSize: 20,
marginLeft: 0,
marginRight: 0,
},
'NativeBase.IconNB': {
alignSelf: 'center',
fontSize: 20,
marginLeft: 0,
marginRight: 0,
},
},
};
return fabTheme;
};

117
templates/app/react-native/src/theme/components/Footer.js

@ -0,0 +1,117 @@
// @flow
import variable from '../variables/Platform';
import { PLATFORM } from '../variables/CommonColor';
export default (variables /* : * */ = variable) => {
const { platformStyle } = variables;
const { platform } = variables;
const iconCommon = {
'NativeBase.Icon': {
color: variables.tabBarActiveTextColor,
},
};
const iconNBCommon = {
'NativeBase.IconNB': {
color: variables.tabBarActiveTextColor,
},
};
const textCommon = {
'NativeBase.Text': {
color: variables.tabBarActiveTextColor,
},
};
const footerTheme = {
'NativeBase.Left': {
'NativeBase.Button': {
'.transparent': {
backgroundColor: 'transparent',
borderColor: null,
elevation: 0,
shadowColor: null,
shadowOffset: null,
shadowRadius: null,
shadowOpacity: null,
...iconCommon,
...iconNBCommon,
...textCommon,
},
alignSelf: null,
...iconCommon,
...iconNBCommon,
// ...textCommon
},
flex: 1,
alignSelf: 'center',
alignItems: 'flex-start',
},
'NativeBase.Body': {
flex: 1,
alignItems: 'center',
alignSelf: 'center',
flexDirection: 'row',
'NativeBase.Button': {
alignSelf: 'center',
'.transparent': {
backgroundColor: 'transparent',
borderColor: null,
elevation: 0,
shadowColor: null,
shadowOffset: null,
shadowRadius: null,
shadowOpacity: null,
...iconCommon,
...iconNBCommon,
...textCommon,
},
'.full': {
height: variables.footerHeight,
paddingBottom: variables.footerPaddingBottom,
flex: 1,
},
...iconCommon,
...iconNBCommon,
// ...textCommon
},
},
'NativeBase.Right': {
'NativeBase.Button': {
'.transparent': {
backgroundColor: 'transparent',
borderColor: null,
elevation: 0,
shadowColor: null,
shadowOffset: null,
shadowRadius: null,
shadowOpacity: null,
...iconCommon,
...iconNBCommon,
...textCommon,
},
alignSelf: null,
...iconCommon,
...iconNBCommon,
// ...textCommon
},
flex: 1,
alignSelf: 'center',
alignItems: 'flex-end',
},
backgroundColor: variables.footerDefaultBg,
flexDirection: 'row',
justifyContent: 'center',
borderTopWidth:
platform === PLATFORM.IOS && platformStyle !== PLATFORM.MATERIAL
? variables.borderWidth
: undefined,
borderColor:
platform === PLATFORM.IOS && platformStyle !== PLATFORM.MATERIAL ? '#cbcbcb' : undefined,
height: variables.footerHeight,
paddingBottom: variables.footerPaddingBottom,
elevation: 3,
left: 0,
right: 0,
};
return footerTheme;
};

78
templates/app/react-native/src/theme/components/FooterTab.js

@ -0,0 +1,78 @@
// @flow
import { Platform } from 'react-native';
import variable from '../variables/Platform';
import { PLATFORM } from '../variables/CommonColor';
export default (variables /* : * */ = variable) => {
const { platform } = variables;
const footerTabTheme = {
'NativeBase.Button': {
'.active': {
'NativeBase.Text': {
color: variables.tabBarActiveTextColor,
fontSize: variables.tabBarTextSize,
lineHeight: 16,
},
'NativeBase.Icon': {
color: variables.tabBarActiveTextColor,
},
'NativeBase.IconNB': {
color: variables.tabBarActiveTextColor,
},
backgroundColor: variables.tabActiveBgColor,
},
flexDirection: null,
backgroundColor: 'transparent',
borderColor: null,
elevation: 0,
shadowColor: null,
shadowOffset: null,
shadowRadius: null,
shadowOpacity: null,
alignSelf: 'center',
flex: 1,
height: variables.footerHeight,
justifyContent: 'center',
'.badge': {
'NativeBase.Badge': {
'NativeBase.Text': {
fontSize: 11,
fontWeight: platform === PLATFORM.IOS ? '600' : undefined,
lineHeight: 14,
},
top: -3,
alignSelf: 'center',
left: 10,
zIndex: 99,
height: 18,
padding: 1.7,
paddingHorizontal: 3,
},
'NativeBase.Icon': {
marginTop: -18,
},
},
'NativeBase.Icon': {
color: variables.tabBarTextColor,
},
'NativeBase.IconNB': {
color: variables.tabBarTextColor,
},
'NativeBase.Text': {
color: variables.tabBarTextColor,
fontSize: variables.tabBarTextSize,
lineHeight: 16,
},
},
backgroundColor: Platform.OS === PLATFORM.ANDROID ? variables.footerDefaultBg : undefined,
flexDirection: 'row',
justifyContent: 'space-between',
flex: 1,
alignSelf: 'stretch',
};
return footerTabTheme;
};

86
templates/app/react-native/src/theme/components/Form.js

@ -0,0 +1,86 @@
// @flow
export default () => {
const theme = {
'NativeBase.Item': {
'.fixedLabel': {
'NativeBase.Label': {
paddingLeft: null,
},
marginLeft: 15,
},
'.inlineLabel': {
'NativeBase.Label': {
paddingLeft: null,
},
marginLeft: 15,
},
'.placeholderLabel': {
'NativeBase.Input': {},
},
'.stackedLabel': {
'NativeBase.Label': {
top: 5,
paddingLeft: null,
},
'NativeBase.Input': {
paddingLeft: null,
marginLeft: null,
},
'NativeBase.Icon': {
marginTop: 36,
},
marginLeft: 15,
},
'.floatingLabel': {
'NativeBase.Input': {
paddingLeft: null,
top: 10,
marginLeft: null,
},
'NativeBase.Label': {
left: 0,
top: 6,
},
'NativeBase.Icon': {
top: 6,
},
marginTop: 15,
marginLeft: 15,
},
'.regular': {
'NativeBase.Label': {
left: 0,
},
marginLeft: 0,
},
'.rounded': {
'NativeBase.Label': {
left: 0,
},
marginLeft: 0,
},
'.underline': {
'NativeBase.Label': {
left: 0,
top: 0,
position: 'relative',
},
'NativeBase.Input': {
left: -15,
},
marginLeft: 15,
},
'.last': {
marginLeft: 0,
paddingLeft: 15,
},
'NativeBase.Label': {
paddingRight: 5,
},
marginLeft: 15,
},
};
return theme;
};

13
templates/app/react-native/src/theme/components/H1.js

@ -0,0 +1,13 @@
// @flow
import variable from '../variables/Platform';
export default (variables /* : * */ = variable) => {
const h1Theme = {
color: variables.textColor,
fontSize: variables.fontSizeH1,
lineHeight: variables.lineHeightH1,
};
return h1Theme;
};

13
templates/app/react-native/src/theme/components/H2.js

@ -0,0 +1,13 @@
// @flow
import variable from '../variables/Platform';
export default (variables /* : * */ = variable) => {
const h2Theme = {
color: variables.textColor,
fontSize: variables.fontSizeH2,
lineHeight: variables.lineHeightH2,
};
return h2Theme;
};

13
templates/app/react-native/src/theme/components/H3.js

@ -0,0 +1,13 @@
// @flow
import variable from '../variables/Platform';
export default (variables /* : * */ = variable) => {
const h3Theme = {
color: variables.textColor,
fontSize: variables.fontSizeH3,
lineHeight: variables.lineHeightH3,
};
return h3Theme;
};

389
templates/app/react-native/src/theme/components/Header.js

@ -0,0 +1,389 @@
// @flow
import { PixelRatio, StatusBar } from 'react-native';
import variable from '../variables/Platform';
import { PLATFORM } from '../variables/CommonColor';
export default (variables /* : * */ = variable) => {
const { platformStyle } = variables;
const { platform } = variables;
const headerTheme = {
'.span': {
height: 128,
'NativeBase.Left': {
alignSelf: 'flex-start',
},
'NativeBase.Body': {
alignSelf: 'flex-end',
alignItems: 'flex-start',
justifyContent: 'center',
paddingBottom: 26,
},
'NativeBase.Right': {
alignSelf: 'flex-start',
},
},
'.hasSubtitle': {
'NativeBase.Body': {
'NativeBase.Title': {
fontSize: variables.titleFontSize - 2,
fontFamily: variables.titleFontfamily,
textAlign: 'center',
fontWeight: '500',
paddingBottom: 3,
},
'NativeBase.Subtitle': {
fontSize: variables.subTitleFontSize,
fontFamily: variables.titleFontfamily,
color: variables.subtitleColor,
textAlign: 'center',
},
},
},
'.transparent': {
backgroundColor: 'transparent',
borderBottomColor: 'transparent',
elevation: 0,
shadowColor: null,
shadowOffset: null,
shadowRadius: null,
shadowOpacity: null,
paddingTop: platform === PLATFORM.ANDROID ? StatusBar.currentHeight : undefined,
height:
platform === PLATFORM.ANDROID
? variables.toolbarHeight + StatusBar.currentHeight
: variables.toolbarHeight,
},
'.noShadow': {
elevation: 0,
shadowColor: null,
shadowOffset: null,
shadowRadius: null,
shadowOpacity: null,
},
'.hasTabs': {
elevation: 0,
shadowColor: null,
shadowOffset: null,
shadowRadius: null,
shadowOpacity: null,
borderBottomWidth: null,
},
'.hasSegment': {
elevation: 0,
shadowColor: null,
shadowOffset: null,
shadowRadius: null,
shadowOpacity: null,
borderBottomWidth: null,
'NativeBase.Left': {
flex: 0.3,
},
'NativeBase.Right': {
flex: 0.3,
},
'NativeBase.Body': {
flex: 1,
'NativeBase.Segment': {
marginRight: 0,
alignSelf: 'center',
'NativeBase.Button': {
paddingLeft: 0,
paddingRight: 0,
},
},
},
},
'.noLeft': {
'NativeBase.Left': {
width: platform === PLATFORM.IOS ? undefined : 0,
flex: platform === PLATFORM.IOS ? 1 : 0,
},
'NativeBase.Body': {
'NativeBase.Title': {
paddingLeft: platform === PLATFORM.IOS ? undefined : 10,
},
'NativeBase.Subtitle': {
paddingLeft: platform === PLATFORM.IOS ? undefined : 10,
},
},
},
'NativeBase.Button': {
justifyContent: 'center',
alignSelf: 'center',
alignItems: 'center',
'.transparent': {
'NativeBase.Text': {
color: variables.toolbarBtnTextColor,
fontWeight: '600',
},
'NativeBase.Icon': {
color: variables.toolbarBtnColor,
},
'NativeBase.IconNB': {
color: variables.toolbarBtnColor,
},
paddingHorizontal: variables.buttonPadding,
},
paddingHorizontal: 15,
},
'.searchBar': {
'NativeBase.Item': {
'NativeBase.Icon': {
backgroundColor: 'transparent',
color: variables.dropdownLinkColor,
fontSize: variables.toolbarSearchIconSize,
alignItems: 'center',
marginTop: 2,
paddingRight: 10,
paddingLeft: 10,
},
'NativeBase.IconNB': {
backgroundColor: 'transparent',
color: null,
alignSelf: 'center',
},
'NativeBase.Input': {
alignSelf: 'center',
lineHeight: null,
height: variables.searchBarInputHeight,
},
alignSelf: 'center',
alignItems: 'center',
justifyContent: 'flex-start',
flex: 1,
height: variables.searchBarHeight,
borderColor: 'transparent',
backgroundColor: variables.toolbarInputColor,
},
'NativeBase.Button': {
'.transparent': {
'NativeBase.Text': {
fontWeight: '500',
},
paddingHorizontal: null,
paddingLeft: platform === PLATFORM.IOS ? 10 : null,
},
paddingHorizontal: platform === PLATFORM.IOS ? undefined : null,
width: platform === PLATFORM.IOS ? undefined : 0,
height: platform === PLATFORM.IOS ? undefined : 0,
},
},
'.rounded': {
'NativeBase.Item': {
borderRadius: platform === PLATFORM.IOS && platformStyle !== PLATFORM.MATERIAL ? 25 : 3,
},
},
'NativeBase.Left': {
'NativeBase.Button': {
'.hasText': {
marginLeft: -10,
height: 30,
'NativeBase.Icon': {
color: variables.toolbarBtnColor,
fontSize: variables.iconHeaderSize,
marginTop: 2,
marginRight: 5,
marginLeft: 2,
},
'NativeBase.Text': {
color: variables.toolbarBtnTextColor,
fontSize: platform === PLATFORM.IOS ? 17 : 0,
marginLeft: 7,
lineHeight: 19.5,
},
'NativeBase.IconNB': {
color: variables.toolbarBtnColor,
fontSize: variables.iconHeaderSize,
marginTop: 2,
marginRight: 5,
marginLeft: 2,
},
},
'.transparent': {
marginLeft: platform === PLATFORM.IOS && platformStyle !== PLATFORM.MATERIAL ? -3 : 0,
'NativeBase.Icon': {
color: variables.toolbarBtnColor,
fontSize:
platform === PLATFORM.IOS && variables.platformStyle !== PLATFORM.MATERIAL
? variables.iconHeaderSize + 1
: variables.iconHeaderSize,
marginTop: 0,
marginRight: 2,
marginLeft: 1,
paddingTop: 1,
},
'NativeBase.IconNB': {
color: variables.toolbarBtnColor,
fontSize:
platform === PLATFORM.IOS && variables.platformStyle !== PLATFORM.MATERIAL
? variables.iconHeaderSize + 1
: variables.iconHeaderSize - 2,
marginTop: 0,
marginRight: 2,
marginLeft: 1,
paddingTop: 1,
},
'NativeBase.Text': {
color: variables.toolbarBtnTextColor,
fontSize: platform === PLATFORM.IOS ? 17 : 0,
top: platform === PLATFORM.IOS ? 1 : -1.5,
paddingLeft: platform === PLATFORM.IOS && platformStyle !== PLATFORM.MATERIAL ? 2 : 5,
paddingRight:
platform === PLATFORM.IOS && platformStyle !== PLATFORM.MATERIAL ? undefined : 10,
},
backgroundColor: 'transparent',
borderColor: null,
elevation: 0,
shadowColor: null,
shadowOffset: null,
shadowRadius: null,
shadowOpacity: null,
},
'NativeBase.Icon': {
color: variables.toolbarBtnColor,
},
'NativeBase.IconNB': {
color: variables.toolbarBtnColor,
},
alignSelf: null,
paddingRight: variables.buttonPadding,
paddingLeft: platform === PLATFORM.IOS && platformStyle !== PLATFORM.MATERIAL ? 4 : 8,
},
flex: platform === PLATFORM.IOS && platformStyle !== PLATFORM.MATERIAL ? 1 : 0.4,
alignSelf: 'center',
alignItems: 'flex-start',
},
'NativeBase.Body': {
flex: 1,
alignItems:
platform === PLATFORM.IOS && platformStyle !== PLATFORM.MATERIAL ? 'center' : 'flex-start',
alignSelf: 'center',
'NativeBase.Segment': {
borderWidth: 0,
alignSelf: 'flex-end',
marginRight: platform === PLATFORM.IOS ? -40 : -55,
},
'NativeBase.Button': {
alignSelf: 'center',
'.transparent': {
backgroundColor: 'transparent',
},
'NativeBase.Icon': {
color: variables.toolbarBtnColor,
},
'NativeBase.IconNB': {
color: variables.toolbarBtnColor,
},
'NativeBase.Text': {
color: variables.inverseTextColor,
backgroundColor: 'transparent',
},
},
},
'NativeBase.Right': {
'NativeBase.Button': {
'.hasText': {
height: 30,
'NativeBase.Icon': {
color: variables.toolbarBtnColor,
fontSize: variables.iconHeaderSize - 2,
marginTop: 2,
marginRight: 2,
marginLeft: 5,
},
'NativeBase.Text': {
color: variables.toolbarBtnTextColor,
fontSize: platform === PLATFORM.IOS ? 17 : 14,
lineHeight: 19.5,
},
'NativeBase.IconNB': {
color: variables.toolbarBtnColor,
fontSize: variables.iconHeaderSize - 2,
marginTop: 2,
marginRight: 2,
marginLeft: 5,
},
},
'.transparent': {
marginRight: platform === PLATFORM.IOS ? -9 : -5,
paddingLeft: 15,
paddingRight: 12,
paddingHorizontal: 15,
borderRadius: 50,
'NativeBase.Icon': {
color: variables.toolbarBtnColor,
fontSize: variables.iconHeaderSize - 2,
marginTop: 0,
marginLeft: 2,
marginRight: 0,
// paddingTop: 0
},
'NativeBase.IconNB': {
color: variables.toolbarBtnColor,
fontSize: variables.iconHeaderSize - 2,
marginTop: 0,
marginLeft: 2,
marginRight: 0,
// paddingTop: 0
},
'NativeBase.Text': {
color: variables.toolbarBtnTextColor,
fontSize: platform === PLATFORM.IOS ? 17 : 14,
top: platform === PLATFORM.IOS ? 1 : -1.5,
paddingRight:
platform === PLATFORM.IOS && variables.platformStyle !== PLATFORM.MATERIAL
? 0
: undefined,
},
backgroundColor: 'transparent',
borderColor: null,
elevation: 0,
shadowColor: null,
shadowOffset: null,
shadowRadius: null,
shadowOpacity: null,
},
'NativeBase.Icon': {
color: variables.toolbarBtnColor,
},
'NativeBase.IconNB': {
color: variables.toolbarBtnColor,
},
alignSelf: null,
paddingHorizontal: variables.buttonPadding,
},
flex: 1,
alignSelf: 'center',
alignItems: 'flex-end',
flexDirection: 'row',
justifyContent: 'flex-end',
},
backgroundColor: variables.toolbarDefaultBg,
flexDirection: 'row',
// paddingHorizontal: 10,
paddingLeft:
platform === PLATFORM.IOS && variables.platformStyle !== PLATFORM.MATERIAL ? 6 : 10,
paddingRight: 10,
justifyContent: 'center',
paddingTop: platform === PLATFORM.IOS ? 18 : 0,
borderBottomWidth: platform === PLATFORM.IOS ? 1 / PixelRatio.getPixelSizeForLayoutSize(1) : 0,
borderBottomColor: variables.toolbarDefaultBorder,
height:
variables.platform === PLATFORM.IOS && variables.platformStyle === PLATFORM.MATERIAL
? variables.toolbarHeight + 10
: variables.toolbarHeight,
elevation: 3,
shadowColor: platformStyle === PLATFORM.MATERIAL ? '#000' : undefined,
shadowOffset: platformStyle === PLATFORM.MATERIAL ? { width: 0, height: 2 } : undefined,
shadowOpacity: platformStyle === PLATFORM.MATERIAL ? 0.2 : undefined,
shadowRadius: platformStyle === PLATFORM.MATERIAL ? 1.2 : undefined,
top: 0,
left: 0,
right: 0,
};
return headerTheme;
};

12
templates/app/react-native/src/theme/components/Icon.js

@ -0,0 +1,12 @@
// @flow
import variable from '../variables/Platform';
export default (variables /* : * */ = variable) => {
const iconTheme = {
fontSize: variables.iconFontSize,
color: variable.textColor,
};
return iconTheme;
};

19
templates/app/react-native/src/theme/components/Input.js

@ -0,0 +1,19 @@
// @flow
import variable from '../variables/Platform';
export default (variables /* : * */ = variable) => {
const inputTheme = {
'.multiline': {
height: null,
},
height: variables.inputHeightBase,
color: variables.inputColor,
paddingLeft: 5,
paddingRight: 5,
flex: 1,
fontSize: variables.inputFontSize,
};
return inputTheme;
};

132
templates/app/react-native/src/theme/components/InputGroup.js

@ -0,0 +1,132 @@
// @flow
import variable from '../variables/Platform';
export default (variables /* : * */ = variable) => {
const inputGroupTheme = {
'NativeBase.Icon': {
fontSize: 24,
color: variables.sTabBarActiveTextColor,
paddingHorizontal: 5,
},
'NativeBase.IconNB': {
fontSize: 24,
color: variables.sTabBarActiveTextColor,
paddingHorizontal: 5,
},
'NativeBase.Input': {
height: variables.inputHeightBase,
color: variables.inputColor,
paddingLeft: 5,
paddingRight: 5,
flex: 1,
fontSize: variables.inputFontSize,
lineHeight: variables.inputLineHeight,
},
'.underline': {
'.success': {
borderColor: variables.inputSuccessBorderColor,
},
'.error': {
borderColor: variables.inputErrorBorderColor,
},
paddingLeft: 5,
borderWidth: variables.borderWidth,
borderTopWidth: 0,
borderRightWidth: 0,
borderLeftWidth: 0,
borderColor: variables.inputBorderColor,
},
'.regular': {
'.success': {
borderColor: variables.inputSuccessBorderColor,
},
'.error': {
borderColor: variables.inputErrorBorderColor,
},
paddingLeft: 5,
borderWidth: variables.borderWidth,
borderColor: variables.inputBorderColor,
},
'.rounded': {
'.success': {
borderColor: variables.inputSuccessBorderColor,
},
'.error': {
borderColor: variables.inputErrorBorderColor,
},
paddingLeft: 5,
borderWidth: variables.borderWidth,
borderRadius: variables.inputGroupRoundedBorderRadius,
borderColor: variables.inputBorderColor,
},
'.success': {
'NativeBase.Icon': {
color: variables.inputSuccessBorderColor,
},
'NativeBase.IconNB': {
color: variables.inputSuccessBorderColor,
},
'.rounded': {
borderRadius: 30,
borderColor: variables.inputSuccessBorderColor,
},
'.regular': {
borderColor: variables.inputSuccessBorderColor,
},
'.underline': {
borderWidth: variables.borderWidth,
borderTopWidth: 0,
borderRightWidth: 0,
borderLeftWidth: 0,
borderColor: variables.inputSuccessBorderColor,
},
borderColor: variables.inputSuccessBorderColor,
},
'.error': {
'NativeBase.Icon': {
color: variables.inputErrorBorderColor,
},
'NativeBase.IconNB': {
color: variables.inputErrorBorderColor,
},
'.rounded': {
borderRadius: 30,
borderColor: variables.inputErrorBorderColor,
},
'.regular': {
borderColor: variables.inputErrorBorderColor,
},
'.underline': {
borderWidth: variables.borderWidth,
borderTopWidth: 0,
borderRightWidth: 0,
borderLeftWidth: 0,
borderColor: variables.inputErrorBorderColor,
},
borderColor: variables.inputErrorBorderColor,
},
'.disabled': {
'NativeBase.Icon': {
color: '#384850',
},
'NativeBase.IconNB': {
color: '#384850',
},
},
paddingLeft: 5,
borderWidth: variables.borderWidth,
borderTopWidth: 0,
borderRightWidth: 0,
borderLeftWidth: 0,
borderColor: variables.inputBorderColor,
backgroundColor: 'transparent',
flexDirection: 'row',
alignItems: 'center',
};
return inputGroupTheme;
};

241
templates/app/react-native/src/theme/components/Item.js

@ -0,0 +1,241 @@
// @flow
import { Platform } from 'react-native';
import variable from '../variables/Platform';
import { PLATFORM } from '../variables/CommonColor';
export default (variables /* : * */ = variable) => {
const itemTheme = {
'.floatingLabel': {
'NativeBase.Input': {
height: 50,
top: 8,
paddingTop: 3,
paddingBottom: 7,
'.multiline': {
minHeight: variables.inputHeightBase,
paddingTop: Platform.OS === PLATFORM.IOS ? 10 : 3,
paddingBottom: Platform.OS === PLATFORM.IOS ? 14 : 10,
},
},
'NativeBase.Label': {
paddingTop: 5,
},
'NativeBase.Icon': {
top: 6,
paddingTop: 8,
},
'NativeBase.IconNB': {
top: 6,
paddingTop: 8,
},
},
'.fixedLabel': {
'NativeBase.Label': {
position: null,
top: null,
left: null,
right: null,
flex: 1,
height: null,
width: null,
fontSize: variables.inputFontSize,
},
'NativeBase.Input': {
flex: 2,
fontSize: variables.inputFontSize,
},
},
'.stackedLabel': {
'NativeBase.Label': {
position: null,
top: null,
left: null,
right: null,
paddingTop: 5,
alignSelf: 'flex-start',
fontSize: variables.inputFontSize - 2,
},
'NativeBase.Icon': {
marginTop: 36,
},
'NativeBase.Input': {
alignSelf: Platform.OS === PLATFORM.IOS ? 'stretch' : 'flex-start',
flex: 1,
width: Platform.OS === PLATFORM.IOS ? null : variables.deviceWidth - 25,
fontSize: variables.inputFontSize,
lineHeight: variables.inputLineHeight - 6,
'.secureTextEntry': {
fontSize: variables.inputFontSize,
},
'.multiline': {
paddingTop: Platform.OS === PLATFORM.IOS ? 9 : undefined,
paddingBottom: Platform.OS === PLATFORM.IOS ? 9 : undefined,
},
},
flexDirection: null,
minHeight: variables.inputHeightBase + 15,
},
'.inlineLabel': {
'NativeBase.Label': {
position: null,
top: null,
left: null,
right: null,
paddingRight: 20,
height: null,
width: null,
fontSize: variables.inputFontSize,
},
'NativeBase.Input': {
paddingLeft: 5,
fontSize: variables.inputFontSize,
},
flexDirection: 'row',
},
'NativeBase.Label': {
fontSize: variables.inputFontSize,
color: variables.inputColorPlaceholder,
paddingRight: 5,
},
'NativeBase.Icon': {
fontSize: 24,
paddingRight: 8,
},
'NativeBase.IconNB': {
fontSize: 24,
paddingRight: 8,
},
'NativeBase.Input': {
'.multiline': {
height: null,
},
height: variables.inputHeightBase,
color: variables.inputColor,
flex: 1,
top: Platform.OS === PLATFORM.IOS ? 1.5 : undefined,
fontSize: variables.inputFontSize,
},
'.underline': {
'NativeBase.Input': {
paddingLeft: 15,
},
'.success': {
borderColor: variables.inputSuccessBorderColor,
},
'.error': {
borderColor: variables.inputErrorBorderColor,
},
borderWidth: variables.borderWidth * 2,
borderTopWidth: 0,
borderRightWidth: 0,
borderLeftWidth: 0,
borderColor: variables.inputBorderColor,
},
'.regular': {
'NativeBase.Input': {
paddingLeft: 8,
},
'NativeBase.Icon': {
paddingLeft: 10,
},
'.success': {
borderColor: variables.inputSuccessBorderColor,
},
'.error': {
borderColor: variables.inputErrorBorderColor,
},
borderWidth: variables.borderWidth * 2,
borderColor: variables.inputBorderColor,
},
'.rounded': {
'NativeBase.Input': {
paddingLeft: 8,
},
'NativeBase.Icon': {
paddingLeft: 10,
},
'.success': {
borderColor: variables.inputSuccessBorderColor,
},
'.error': {
borderColor: variables.inputErrorBorderColor,
},
borderWidth: variables.borderWidth * 2,
borderRadius: 30,
borderColor: variables.inputBorderColor,
},
'.success': {
'NativeBase.Icon': {
color: variables.inputSuccessBorderColor,
},
'NativeBase.IconNB': {
color: variables.inputSuccessBorderColor,
},
'.rounded': {
borderRadius: 30,
borderColor: variables.inputSuccessBorderColor,
},
'.regular': {
borderColor: variables.inputSuccessBorderColor,
},
'.underline': {
borderWidth: variables.borderWidth * 2,
borderTopWidth: 0,
borderRightWidth: 0,
borderLeftWidth: 0,
borderColor: variables.inputSuccessBorderColor,
},
borderColor: variables.inputSuccessBorderColor,
},
'.error': {
'NativeBase.Icon': {
color: variables.inputErrorBorderColor,
},
'NativeBase.IconNB': {
color: variables.inputErrorBorderColor,
},
'.rounded': {
borderRadius: 30,
borderColor: variables.inputErrorBorderColor,
},
'.regular': {
borderColor: variables.inputErrorBorderColor,
},
'.underline': {
borderWidth: variables.borderWidth * 2,
borderTopWidth: 0,
borderRightWidth: 0,
borderLeftWidth: 0,
borderColor: variables.inputErrorBorderColor,
},
borderColor: variables.inputErrorBorderColor,
},
'.disabled': {
'NativeBase.Icon': {
color: '#384850',
},
'NativeBase.IconNB': {
color: '#384850',
},
},
'.picker': {
marginLeft: 0,
},
borderWidth: variables.borderWidth * 2,
borderTopWidth: 0,
borderRightWidth: 0,
borderLeftWidth: 0,
borderColor: variables.inputBorderColor,
backgroundColor: 'transparent',
flexDirection: 'row',
alignItems: 'center',
marginLeft: 2,
};
return itemTheme;
};

15
templates/app/react-native/src/theme/components/Label.js

@ -0,0 +1,15 @@
// @flow
export default () => {
const labelTheme = {
'.focused': {
width: 0,
},
'.textPrimary': {
color: 'gray',
},
fontSize: 17,
};
return labelTheme;
};

11
templates/app/react-native/src/theme/components/Left.js

@ -0,0 +1,11 @@
// @flow
export default () => {
const leftTheme = {
flex: 1,
alignSelf: 'center',
alignItems: 'flex-start',
};
return leftTheme;
};

441
templates/app/react-native/src/theme/components/ListItem.js

@ -0,0 +1,441 @@
// @flow
import { Platform, PixelRatio } from 'react-native';
import pickerTheme from './Picker';
import variable from '../variables/Platform';
import { PLATFORM } from '../variables/CommonColor';
export default (variables /* : * */ = variable) => {
const { platform } = variables;
const selectedStyle = {
'NativeBase.Text': {
color: variables.listItemSelected,
},
'NativeBase.Icon': {
color: variables.listItemSelected,
},
};
const listItemTheme = {
'NativeBase.InputGroup': {
'NativeBase.Icon': {
paddingRight: 5,
},
'NativeBase.IconNB': {
paddingRight: 5,
},
'NativeBase.Input': {
paddingHorizontal: 5,
},
flex: 1,
borderWidth: null,
margin: -10,
borderBottomColor: 'transparent',
},
'.searchBar': {
'NativeBase.Item': {
'NativeBase.Icon': {
backgroundColor: 'transparent',
color: variables.dropdownLinkColor,
fontSize:
platform === PLATFORM.IOS ? variables.iconFontSize - 10 : variables.iconFontSize - 5,
alignItems: 'center',
marginTop: 2,
paddingRight: 8,
},
'NativeBase.IconNB': {
backgroundColor: 'transparent',
color: null,
alignSelf: 'center',
},
'NativeBase.Input': {
alignSelf: 'center',
},
alignSelf: 'center',
alignItems: 'center',
justifyContent: 'flex-start',
flex: 1,
height: platform === PLATFORM.IOS ? 30 : 40,
borderColor: 'transparent',
backgroundColor: '#fff',
borderRadius: 5,
},
'NativeBase.Button': {
'.transparent': {
'NativeBase.Text': {
fontWeight: '500',
},
paddingHorizontal: null,
paddingLeft: platform === PLATFORM.IOS ? 10 : null,
},
paddingHorizontal: platform === PLATFORM.IOS ? undefined : null,
width: platform === PLATFORM.IOS ? undefined : 0,
height: platform === PLATFORM.IOS ? undefined : 0,
},
backgroundColor: variables.toolbarInputColor,
padding: 10,
marginLeft: null,
},
'NativeBase.CheckBox': {
marginLeft: -10,
marginRight: 10,
},
'.first': {
'.itemHeader': {
paddingTop: variables.listItemPadding + 3,
},
},
'.itemHeader': {
'.first': {
paddingTop: variables.listItemPadding + 3,
},
borderBottomWidth: platform === PLATFORM.IOS ? variables.borderWidth : null,
marginLeft: null,
padding: variables.listItemPadding,
paddingLeft: variables.listItemPadding + 5,
paddingTop: platform === PLATFORM.IOS ? variables.listItemPadding + 25 : undefined,
paddingBottom: platform === PLATFORM.ANDROID ? variables.listItemPadding + 20 : undefined,
flexDirection: 'row',
borderColor: variables.listBorderColor,
'NativeBase.Text': {
fontSize: 14,
color: platform === PLATFORM.IOS ? undefined : variables.listNoteColor,
},
},
'.itemDivider': {
borderBottomWidth: null,
marginLeft: null,
padding: variables.listItemPadding,
paddingLeft: variables.listItemPadding + 5,
backgroundColor: variables.listDividerBg,
flexDirection: 'row',
borderColor: variables.listBorderColor,
},
'.selected': {
'NativeBase.Left': {
...selectedStyle,
},
'NativeBase.Body': {
...selectedStyle,
},
'NativeBase.Right': {
...selectedStyle,
},
...selectedStyle,
},
'NativeBase.Left': {
'NativeBase.Body': {
'NativeBase.Text': {
'.note': {
color: variables.listNoteColor,
fontWeight: '200',
},
fontWeight: '600',
},
marginLeft: 10,
alignItems: null,
alignSelf: null,
},
'NativeBase.Icon': {
width: variables.iconFontSize - 10,
fontSize: variables.iconFontSize - 10,
},
'NativeBase.IconNB': {
width: variables.iconFontSize - 10,
fontSize: variables.iconFontSize - 10,
},
'NativeBase.Text': {
alignSelf: 'center',
},
flexDirection: 'row',
},
'NativeBase.Body': {
'NativeBase.Text': {
marginHorizontal: variables.listItemPadding,
'.note': {
color: variables.listNoteColor,
fontWeight: '200',
},
},
alignSelf: null,
alignItems: null,
},
'NativeBase.Right': {
'NativeBase.Badge': {
alignSelf: null,
},
'NativeBase.PickerNB': {
'NativeBase.Button': {
marginRight: -15,
'NativeBase.Text': {
color: variables.topTabBarActiveTextColor,
},
},
},
'NativeBase.Button': {
alignSelf: null,
'.transparent': {
'NativeBase.Text': {
color: variables.topTabBarActiveTextColor,
},
},
},
'NativeBase.Icon': {
alignSelf: null,
fontSize: variables.iconFontSize - 8,
color: '#c9c8cd',
},
'NativeBase.IconNB': {
alignSelf: null,
fontSize: variables.iconFontSize - 8,
color: '#c9c8cd',
},
'NativeBase.Text': {
'.note': {
color: variables.listNoteColor,
fontWeight: '200',
},
alignSelf: null,
},
'NativeBase.Thumbnail': {
alignSelf: null,
},
'NativeBase.Image': {
alignSelf: null,
},
'NativeBase.Radio': {
alignSelf: null,
},
'NativeBase.Checkbox': {
alignSelf: null,
},
'NativeBase.Switch': {
alignSelf: null,
},
padding: null,
flex: 0.28,
},
'NativeBase.Text': {
'.note': {
color: variables.listNoteColor,
fontWeight: '200',
},
alignSelf: 'center',
},
'.last': {
marginLeft: -(variables.listItemPadding + 5),
paddingLeft: (variables.listItemPadding + 5) * 2,
top: 1,
},
'.avatar': {
'NativeBase.Left': {
flex: 0,
alignSelf: 'flex-start',
paddingTop: 14,
},
'NativeBase.Body': {
'NativeBase.Text': {
marginLeft: null,
},
flex: 1,
paddingVertical: variables.listItemPadding,
borderBottomWidth: variables.borderWidth,
borderColor: variables.listBorderColor,
marginLeft: variables.listItemPadding + 5,
},
'NativeBase.Right': {
'NativeBase.Text': {
'.note': {
fontSize: variables.noteFontSize - 2,
},
},
flex: 0,
paddingRight: variables.listItemPadding + 5,
alignSelf: 'stretch',
paddingVertical: variables.listItemPadding,
borderBottomWidth: variables.borderWidth,
borderColor: variables.listBorderColor,
},
'.noBorder': {
'NativeBase.Body': {
borderBottomWidth: null,
},
'NativeBase.Right': {
borderBottomWidth: null,
},
},
borderBottomWidth: null,
paddingVertical: null,
paddingRight: null,
},
'.thumbnail': {
'NativeBase.Left': {
flex: 0,
},
'NativeBase.Body': {
'NativeBase.Text': {
marginLeft: null,
},
flex: 1,
paddingVertical: variables.listItemPadding + 8,
borderBottomWidth: variables.borderWidth,
borderColor: variables.listBorderColor,
marginLeft: variables.listItemPadding + 5,
},
'NativeBase.Right': {
'NativeBase.Button': {
'.transparent': {
'NativeBase.Text': {
fontSize: variables.listNoteSize,
color: variables.sTabBarActiveTextColor,
},
},
height: null,
},
flex: 0,
justifyContent: 'center',
alignSelf: 'stretch',
paddingRight: variables.listItemPadding + 5,
paddingVertical: variables.listItemPadding + 5,
borderBottomWidth: variables.borderWidth,
borderColor: variables.listBorderColor,
},
'.noBorder': {
'NativeBase.Body': {
borderBottomWidth: null,
},
'NativeBase.Right': {
borderBottomWidth: null,
},
},
borderBottomWidth: null,
paddingVertical: null,
paddingRight: null,
},
'.icon': {
'.last': {
'NativeBase.Body': {
borderBottomWidth: null,
},
'NativeBase.Right': {
borderBottomWidth: null,
},
borderBottomWidth: variables.borderWidth,
borderColor: variables.listBorderColor,
},
'NativeBase.Left': {
'NativeBase.Button': {
'NativeBase.IconNB': {
marginHorizontal: null,
fontSize: variables.iconFontSize - 5,
},
'NativeBase.Icon': {
marginHorizontal: null,
fontSize: variables.iconFontSize - 8,
},
alignSelf: 'center',
height: 29,
width: 29,
borderRadius: 6,
paddingVertical: null,
paddingHorizontal: null,
alignItems: 'center',
justifyContent: 'center',
},
'NativeBase.Icon': {
width: variables.iconFontSize - 5,
fontSize: variables.iconFontSize - 2,
},
'NativeBase.IconNB': {
width: variables.iconFontSize - 5,
fontSize: variables.iconFontSize - 2,
},
paddingRight: variables.listItemPadding + 5,
flex: 0,
height: 44,
justifyContent: 'center',
alignItems: 'center',
},
'NativeBase.Body': {
'NativeBase.Text': {
marginLeft: null,
fontSize: 17,
},
flex: 1,
height: 44,
justifyContent: 'center',
borderBottomWidth: 1 / PixelRatio.getPixelSizeForLayoutSize(1),
borderColor: variables.listBorderColor,
},
'NativeBase.Right': {
'NativeBase.Text': {
textAlign: 'center',
color: '#8F8E95',
fontSize: 17,
},
'NativeBase.IconNB': {
color: '#C8C7CC',
fontSize: variables.iconFontSize - 10,
alignSelf: 'center',
paddingLeft: 10,
paddingTop: 3,
},
'NativeBase.Icon': {
color: '#C8C7CC',
fontSize: variables.iconFontSize - 10,
alignSelf: 'center',
paddingLeft: 10,
paddingTop: 3,
},
'NativeBase.Switch': {
marginRight: Platform.OS === PLATFORM.IOS ? undefined : -5,
alignSelf: null,
},
'NativeBase.PickerNB': {
...pickerTheme(),
},
flexDirection: 'row',
alignItems: 'center',
flex: 0,
alignSelf: 'stretch',
height: 44,
justifyContent: 'flex-end',
borderBottomWidth: 1 / PixelRatio.getPixelSizeForLayoutSize(1),
borderColor: variables.listBorderColor,
paddingRight: variables.listItemPadding + 5,
},
'.noBorder': {
'NativeBase.Body': {
borderBottomWidth: null,
},
'NativeBase.Right': {
borderBottomWidth: null,
},
},
borderBottomWidth: null,
paddingVertical: null,
paddingRight: null,
height: 44,
justifyContent: 'center',
},
'.noBorder': {
borderBottomWidth: null,
},
'.noIndent': {
marginLeft: null,
padding: variables.listItemPadding,
paddingLeft: variables.listItemPadding + 6,
},
alignItems: 'center',
flexDirection: 'row',
paddingRight: variables.listItemPadding + 6,
paddingVertical: variables.listItemPadding + 3,
marginLeft: variables.listItemPadding + 6,
borderBottomWidth: 1 / PixelRatio.getPixelSizeForLayoutSize(1),
backgroundColor: variables.listBg,
borderColor: variables.listBorderColor,
};
return listItemTheme;
};

14
templates/app/react-native/src/theme/components/Picker.android.js

@ -0,0 +1,14 @@
// @flow
export default () => {
const pickerTheme = {
'.note': {
color: '#8F8E95',
},
// width: 90,
marginRight: -4,
flexGrow: 1,
};
return pickerTheme;
};

7
templates/app/react-native/src/theme/components/Picker.ios.js

@ -0,0 +1,7 @@
// @flow
export default () => {
const pickerTheme = {};
return pickerTheme;
};

14
templates/app/react-native/src/theme/components/Picker.js

@ -0,0 +1,14 @@
// @flow
export default () => {
const pickerTheme = {
'.note': {
color: '#8F8E95',
},
// width: 90,
marginRight: -4,
flexGrow: 1,
};
return pickerTheme;
};

26
templates/app/react-native/src/theme/components/Radio.js

@ -0,0 +1,26 @@
// @flow
import { Platform } from 'react-native';
import variable from '../variables/Platform';
import { PLATFORM } from '../variables/CommonColor';
export default (variables /* : * */ = variable) => {
const radioTheme = {
'.selected': {
'NativeBase.IconNB': {
color:
Platform.OS === PLATFORM.IOS ? variables.radioColor : variables.radioSelectedColorAndroid,
lineHeight: Platform.OS === PLATFORM.IOS ? 25 : variables.radioBtnLineHeight,
height: Platform.OS === PLATFORM.IOS ? 20 : undefined,
},
},
'NativeBase.IconNB': {
color: Platform.OS === PLATFORM.IOS ? 'transparent' : undefined,
lineHeight: Platform.OS === PLATFORM.IOS ? undefined : variables.radioBtnLineHeight,
fontSize: Platform.OS === PLATFORM.IOS ? undefined : variables.radioBtnSize,
},
};
return radioTheme;
};

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

Loading…
Cancel
Save