Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(farmer): testimonial videos #320

Merged
merged 11 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/picsa-apps/extension-app-native/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ android {
applicationId "io.picsa.extension"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 3043004
versionName "3.43.4"
versionCode 3044000
versionName "3.44.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
<resource-item-file [resource]="videoResource()"></resource-item-file>
@if(videoData()){
<!-- Ignore case where video entry exists but ranking empty (e.g. playlist with multi-country videos) -->
@if(videoResource(); as resource){
<resource-item-file [resource]="resource"></resource-item-file>

} } @else {
<p>Video not found</p>
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { CommonModule } from '@angular/common';
import { Component, computed, input } from '@angular/core';
import { ConfigurationService } from '@picsa/configuration/src';
import { ILocaleCode } from '@picsa/data';
import { IPicsaVideo, IPicsaVideoData } from '@picsa/data/resources';
import { PICSA_FARMER_VIDEO_RESOURCES_HASHMAP } from '@picsa/data/resources';
import { RESOURCE_VIDEO_HASHMAP } from '@picsa/data/resources';
import { ResourcesComponentsModule } from '@picsa/resources/src/app/components/components.module';

/**
Expand All @@ -25,25 +24,51 @@ export class FarmerStepVideoComponent {
videoData = input.required<IPicsaVideoData>();

videoResource = computed(() => {
const { language_code } = this.configurationService.userSettings();
// HACK - when identifying video to show user cannot rely solely on language_code as
// that populates 'global_en' when different country used (should be zm_en)
// So instead use country_code specified and language part of localeCode
const { country_code: userCountry, language_code } = this.configurationService.userSettings();
const [_, userLanguage] = language_code.split('_');
const availableVideos = this.videoData().children;
const video = this.selectDefaultVideo(language_code, availableVideos);
// HACK - lookup resource entry which should be given by same id
const resource = PICSA_FARMER_VIDEO_RESOURCES_HASHMAP[video.id];
return resource;
// HACK - select best video recommendation. TODO - show toggle options in future
const [video] = this.filterAvailableVideos(userCountry, userLanguage, availableVideos);
if (video) {
// HACK - lookup resource entry which should be given by same id
const resource = RESOURCE_VIDEO_HASHMAP[video.id];
return resource;
}
return undefined;
});

constructor(private configurationService: ConfigurationService) {}

private selectDefaultVideo(locale_code: ILocaleCode, videos: IPicsaVideo[]) {
// prioritise video in same locale
const localeVideo = videos.find((v) => v.locale_code === locale_code);
if (localeVideo) return localeVideo;

// TODO - fallback video to same language different locale
// TODO - track preference for video size (when supported in future, currently all 360p)
private filterAvailableVideos(userCountry: string, userLanguage: string, videos: IPicsaVideo[] = []) {
const rankedVideos = videos
.map((v) => ({ ...v, _rank: getVideoRank(userCountry, userLanguage, v) }))
.filter(({ _rank }) => _rank > 0)
.sort((a, b) => a._rank - b._rank);

// default fallback to first video entry
return videos[0];
return rankedVideos;
}
}

function getVideoRank(userCountry: string, userLanguage: string, video: IPicsaVideo) {
const [audio, subtitle] = video.locale_codes;
const [audioCountry, audioLanguage] = audio.split('_');
const subtitleLanguage = subtitle?.split('_')[1];

// 1 - same country and audio
if (audioCountry === userCountry && audioLanguage === userLanguage) return 1;
// 2 - same country and user language subtitle
if (audioCountry === userCountry && subtitleLanguage === userLanguage) return 2;
// 3 - global video with user language audio
if (audioCountry === 'global' && audioLanguage === userLanguage) return 3;
// 4 - global video with user language subtitle
if (audioCountry === 'global' && subtitleLanguage === userLanguage) return 4;
// 5 - return all videos for global users
if (userCountry === 'global') return 5;
// 6 - return global fallback
if (audioCountry === 'global' && audioLanguage === 'en') return 6;
return -1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
<h2 class="step-title">{{ step.title | translate }}</h2>
<div class="tags-container">
@for(tag of step.tags; track tag){
<span class="tag step-tag">{{ tag.label | translate }}</span>
<span class="tag" [attr.data-color]="tag.color">{{ tag.label | translate }}</span>
} @for(tool of step.tools; track tool){
<span class="tag tool-tag">{{ tool.label | translate }}</span>
<span class="tag">{{ tool.label | translate }}</span>
}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,10 @@ button.mat-mdc-icon-button.nav-button {
background: var(--tag-background, black);
padding: 8px;
color: white;
.step-tag {
}
}
.tag.tool-tag {
.tag {
--tag-background: var(--color-primary);
}
.tag.step-tag {
.tag[data-color='secondary'] {
--tag-background: var(--color-secondary);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,26 @@ <h2 class="title">{{ content.title | translate }}</h2>
<!-- video -->
@case ("video") {
<ng-template mat-tab-label>
<mat-icon class="tab-icon">slideshow</mat-icon>
<mat-icon class="tab-icon">{{ step.tabMatIcon || 'slideshow' }}</mat-icon>
{{ step.tabLabel || 'video' | translate }}
</ng-template>
<div class="tab-content">
@if(step.video){
<farmer-step-video [videoData]="step.video"></farmer-step-video>
}
</div>

}
<!-- video playlist -->
@case ("video_playlist") {
<ng-template mat-tab-label>
<mat-icon class="tab-icon">{{ step.tabMatIcon || 'slideshow' }}</mat-icon>
{{ step.tabLabel || 'video' | translate }}
</ng-template>
<div class="tab-content">
@for(video of step.videos; track video.id){
<farmer-step-video [videoData]="video"></farmer-step-video>
}
</div>
} }
</mat-tab>
}
Expand All @@ -55,7 +66,7 @@ <h2 class="title">{{ content.title | translate }}</h2>
</mat-tab>
}
<!-- User photos -->
@if(photoAlbum(); as album){
@if(content.showReviewSection){ @if(photoAlbum(); as album){
<mat-tab>
<ng-template mat-tab-label>
<mat-icon class="tab-icon">perm_media</mat-icon>
Expand All @@ -67,7 +78,7 @@ <h2 class="title">{{ content.title | translate }}</h2>
</div>
</mat-tab>

}
} }
<!-- @if(tools()[1]; as tool_1){
<mat-tab>
<ng-template mat-tab-label>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!-- Ready -->
@if(downloadStatus==='ready'){
@if(downloadStatus==='ready' || downloadStatus==='error'){
<button mat-icon-button (click)="downloadResource()" [attr.data-style-variant]="styleVariant">
<div class="download-button-inner">
<mat-icon style="font-size: 30px">download</mat-icon>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ export class ResourceItemFileComponent implements OnInit, OnDestroy {

async ngOnDestroy() {
// ensure any created file attachment uris disposed of
this.service.revokeFileAttachmentURIs([this.dbDoc.filename]);
if (this.dbDoc) {
this.service.revokeFileAttachmentURIs([this.dbDoc.filename]);
}
}

/** When attachment state changed attempt to get URI to downloaded file resource */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { PICSA_FARMER_VIDEO_RESOURCES_HASHMAP } from '@picsa/data/resources';
import { RESOURCE_VIDEO_HASHMAP } from '@picsa/data/resources';

import { IResourceCollection } from '../../schemas';

/**************************************************************************
* Legacy Resource Format
* Support legacy resources system where each resource child has own db entry
*
* TODO - migrate all resources to use modern format so code below can be removed
***************************************************************************/

const files = Object.keys(RESOURCE_VIDEO_HASHMAP);

/**
* Create a collection to store all farmer videos populated to hardcoded data
*/
Expand All @@ -11,8 +20,11 @@ const picsa_videos_farmer: IResourceCollection = {
type: 'collection',
title: 'Farmer Videos',
description: 'Training videos to support PICSA',
childResources: { collections: [], files: Object.keys(PICSA_FARMER_VIDEO_RESOURCES_HASHMAP), links: [] },
childResources: { collections: [], files, links: [] },
parentCollection: 'picsa_videos',
};

export default { ...PICSA_FARMER_VIDEO_RESOURCES_HASHMAP, picsa_videos_farmer };
export default {
...RESOURCE_VIDEO_HASHMAP,
picsa_videos_farmer,
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Browser } from '@capacitor/browser';
import { Capacitor } from '@capacitor/core';
import { Share } from '@capacitor/share';
import { ConfigurationService } from '@picsa/configuration/src';
import { APP_VERSION } from '@picsa/environments/src';
import { APP_VERSION, ENVIRONMENT } from '@picsa/environments/src';
import { PicsaAsyncService } from '@picsa/shared/services/asyncService.service';
import { AnalyticsService } from '@picsa/shared/services/core/analytics.service';
import { PicsaDatabase_V2_Service, PicsaDatabaseAttachmentService } from '@picsa/shared/services/core/db_v2';
Expand Down Expand Up @@ -162,9 +162,9 @@ export class ResourcesToolService extends PicsaAsyncService {
// TODO - process after cache check
await this.deleteRemovedResources();

// Use caching system to only populate once per app version launch
// Use caching system to only populate once per app version launch in production
const assetsCacheVersion = this.getAssetResourcesVersion();
if (assetsCacheVersion === APP_VERSION.number) {
if (ENVIRONMENT.production && assetsCacheVersion === APP_VERSION.number) {
return;
}
// Update DB with hardcoded entries
Expand Down
14 changes: 11 additions & 3 deletions libs/data/farmer_content/data/content/0_intro.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { marker as translateMarker } from '@biesbjerg/ngx-translate-extract-marker';

import { IFarmerContent, IFarmerContentStep } from '../../types';
import { PICSA_FARMER_VIDEOS_HASHMAP } from '@picsa/data/resources';
import { PICSA_FARMER_VIDEOS_HASHMAP, PICSA_VIDEO_TESTIMONIAL_HASHMAP } from '@picsa/data/resources';

const steps: IFarmerContentStep[] = [{ type: 'video', video: PICSA_FARMER_VIDEOS_HASHMAP.intro }];
const steps: IFarmerContentStep[] = [
{ type: 'video', video: PICSA_FARMER_VIDEOS_HASHMAP.intro, tabLabel: translateMarker('Intro') },
{
type: 'video_playlist',
videos: Object.values(PICSA_VIDEO_TESTIMONIAL_HASHMAP),
tabLabel: translateMarker('Testimonials'),
tabMatIcon: 'people',
},
];

const content: Omit<IFarmerContent, 'id' | 'icon_path'> = {
slug: 'intro',
title: translateMarker('What is PICSA?'),
tools: [],
tags: [{ label: translateMarker('Tutorials') }],
tags: [{ label: translateMarker('Tutorials'), color: 'secondary' }],
steps,
};
export default content;
3 changes: 2 additions & 1 deletion libs/data/farmer_content/data/content/1_what_you_do.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ const content: Omit<IFarmerContent, 'id' | 'icon_path'> = {
slug: 'what-do-you-currently-do',
title: translateMarker('What do you currently do?'),
tools: [seasonal_calendar],
tags: [],
tags: [{ label: translateMarker('Resource Allocation Map') }],
steps,
showReviewSection: true,
};
export default content;
1 change: 1 addition & 0 deletions libs/data/farmer_content/data/content/2_climate_change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ const content: Omit<IFarmerContent, 'id' | 'icon_path'> = {
tools: [climate],
tags: [],
steps,
showReviewSection: true,
};
export default content;
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ const content: Omit<IFarmerContent, 'id' | 'icon_path'> = {
tools: [probability_and_risk],
tags: [],
steps,
showReviewSection: true,
};
export default content;
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ const content: Omit<IFarmerContent, 'id' | 'icon_path'> = {
tools: [options],
tags: [],
steps,
showReviewSection: true,
};
export default content;
1 change: 1 addition & 0 deletions libs/data/farmer_content/data/content/5_compare_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ const content: Omit<IFarmerContent, 'id' | 'icon_path'> = {
tools: [budget],
tags: [],
steps,
showReviewSection: true,
};
export default content;
1 change: 1 addition & 0 deletions libs/data/farmer_content/data/content/6_decide_and_plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ const content: Omit<IFarmerContent, 'id' | 'icon_path'> = {
tags: [],
steps,
disabled: true,
showReviewSection: true,
};
export default content;
1 change: 0 additions & 1 deletion libs/data/farmer_content/data/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const TOOLS_BASE = {
href: 'crop-probability',
tabLabel: translateMarker('Tool'),
},
// resource_allocation_map: { label: translateMarker('Resource Allocation Map'), tabLabel: translateMarker('RAM Tool') },
seasonal_calendar: {
label: translateMarker('Seasonal Calendar'),
href: 'seasonal-calendar',
Expand Down
23 changes: 18 additions & 5 deletions libs/data/farmer_content/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,35 @@ export interface IToolData {
tabLabel?: string;
}

interface IFarmerContentStepVideo {
type: 'video';
video: IPicsaVideoData;
interface IContentStepBase {
type: string;
/** Label to show when selecting content from tab */
tabLabel?: string;
/** Icon to show in tab */
tabMatIcon?: string;
}

interface IFarmerContentStepVideo extends IContentStepBase {
type: 'video';
video: IPicsaVideoData;
}

interface IFarmerContentStepVideoPlaylist extends IContentStepBase {
type: 'video_playlist';
videos: IPicsaVideoData[];
}

export type IFarmerContentStep = IFarmerContentStepVideo;
export type IFarmerContentStep = IFarmerContentStepVideo | IFarmerContentStepVideoPlaylist;

export interface IFarmerContent {
id: IFarmerContentId;
slug: string;
icon_path: string;
title: string;
tools: IToolData[];
tags: { label: string }[];
tags: { label: string; color?: 'primary' | 'secondary' }[];
steps: IFarmerContentStep[];
disabled?: boolean;
/** Include a photo-input section as part of review */
showReviewSection?: boolean;
}
2 changes: 1 addition & 1 deletion libs/data/resources/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './types';
export * from './farmerVideos';
export * from './videos';
6 changes: 5 additions & 1 deletion libs/data/resources/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { ILocaleCode } from '../deployments';

export interface IPicsaVideo {
id: string;
locale_code: ILocaleCode;
/**
* Country and Language codes supported by video.
* The audio locale should be listed first and subtitle second if different
*/
locale_codes: ILocaleCode[];
size_kb: number;
resolution: '360p';
supabase_url: string;
Expand Down
Loading
Loading