Skip to content

Commit

Permalink
Merge pull request #292 from e-picsa/ft-farmer-photo-storage
Browse files Browse the repository at this point in the history
ft(farmer-activity) Support for photo storage
  • Loading branch information
chrismclarke authored Jul 28, 2024
2 parents 8ac49da + c8f6b38 commit 0dc1c09
Show file tree
Hide file tree
Showing 29 changed files with 495 additions and 17 deletions.
2 changes: 1 addition & 1 deletion apps/picsa-apps/extension-app-native/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.1.4'
classpath 'com.android.tools.build:gradle:8.5.0'
classpath 'com.google.gms:google-services:4.4.0'

// NOTE: Do not place your application dependencies here; they belong
Expand Down
5 changes: 5 additions & 0 deletions apps/picsa-apps/extension-app/src/app/routes/app-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export const APP_ROUTES: Routes = [
children: extensionContentRoutes,
title: 'PICSA',
},
// Photos debug page
{
path: 'photos',
loadComponent: () => import('@picsa/shared/features/photo').then((mod) => mod.PhotoDebugComponent),
},

// NOTE - Home not currently working as standalone component so keeping as module
// (possibly needs to import router-outlet or similar for setup)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ <h2 class="title">{{ content.title | translate }}</h2>
</div>
</div>
</mat-tab>
}
<!-- User photos -->
@if(photoAlbum(); as album){
<mat-tab>
<ng-template mat-tab-label>
<mat-icon class="tab-icon">perm_media</mat-icon>
{{ 'Review' | translate }}
</ng-template>
<div class="tab-content router-tab">
<picsa-photo-list [album]="album"></picsa-photo-list>
<picsa-photo-input [album]="album" style="margin-top: 2rem"></picsa-photo-input>
</div>
</mat-tab>

}
<!-- @if(tools()[1]; as tool_1){
<mat-tab>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, effect, signal } from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, effect, signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { MatTabChangeEvent, MatTabsModule } from '@angular/material/tabs';
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';
Expand All @@ -8,13 +8,23 @@ import { FARMER_CONTENT_DATA_BY_SLUG, IFarmerContent, IFarmerContentStep, IToolD
// eslint-disable-next-line @nx/enforce-module-boundaries
import { ResourcesComponentsModule } from '@picsa/resources/src/app/components/components.module';
import { FadeInOut } from '@picsa/shared/animations';
import { PhotoInputComponent, PhotoListComponent,PhotoViewComponent } from '@picsa/shared/features';
import { PicsaTranslateModule } from '@picsa/shared/modules';
import { TourService } from '@picsa/shared/services/core/tour';

@Component({
selector: 'farmer-content-module-home',
standalone: true,
imports: [CommonModule, PicsaTranslateModule, ResourcesComponentsModule, MatTabsModule, RouterOutlet],
imports: [
CommonModule,
PicsaTranslateModule,
ResourcesComponentsModule,
MatTabsModule,
PhotoInputComponent,
PhotoViewComponent,
RouterOutlet,
PhotoListComponent,
],
templateUrl: './module-home.component.html',
styleUrl: './module-home.component.scss',
animations: [FadeInOut({ inSpeed: 200, inDelay: 100 })],
Expand All @@ -27,6 +37,15 @@ export class FarmerContentModuleHomeComponent {
public steps = signal<IFarmerContentStep[]>([]);
public tools = signal<IToolData[]>([]);

/** Store any user-generated photos within a folder named after module */
public photoAlbum = computed(() => {
const content = this.content();
if (content) {
return `farmer-activity/${content.id}`;
}
return undefined;
});

constructor(
private route: ActivatedRoute,
private router: Router,
Expand Down
6 changes: 1 addition & 5 deletions apps/picsa-tools/option-tool/src/app/schemas/schema_v4.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { generateID } from '@picsa/shared/services/core/db/db.service';
import type { IPicsaCollectionCreator } from '@picsa/shared/services/core/db_v2';
import { generateTimestamp, type IPicsaCollectionCreator } from '@picsa/shared/services/core/db_v2';
import { RxJsonSchema } from 'rxdb';

import { IOptionsToolEntry_v3, SCHEMA_V3 } from './schema_v3';

const SCHEMA_VERSION = 4;

const generateTimestamp = (): string => {
return new Date().toISOString();
};

/**
* ADD 'enterprise' and '_created_at' properties
*/
Expand Down
1 change: 1 addition & 0 deletions libs/shared/src/features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export * from './data-table';
export * from './dialog';
export * from './drawing';
export * from './pdf-viewer';
export * from './photo';
export * from './video-player';
4 changes: 4 additions & 0 deletions libs/shared/src/features/photo/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './photo-debug/photo-debug.component';
export * from './photo-input/photo-input.component';
export * from './photo-list/photo-list.component';
export * from './photo-view/photo-view.component';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div class="page-content">
<h2>Photos Debug</h2>
<picsa-photo-list />
<picsa-photo-input album="debug" style="margin-top: 2rem;" />
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { PhotoDebugComponent } from './photo-debug.component';

describe('PhotoDebugComponent', () => {
let component: PhotoDebugComponent;
let fixture: ComponentFixture<PhotoDebugComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PhotoDebugComponent],
}).compileComponents();

fixture = TestBed.createComponent(PhotoDebugComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Component } from '@angular/core';

import { PhotoInputComponent } from '../photo-input/photo-input.component';
import { PhotoListComponent } from '../photo-list/photo-list.component';
import { PhotoViewComponent } from '../photo-view/photo-view.component';

@Component({
selector: 'picsa-photo-debug',
standalone: true,
imports: [PhotoInputComponent, PhotoListComponent, PhotoViewComponent],
templateUrl: './photo-debug.component.html',
styleUrl: './photo-debug.component.scss',
})
export class PhotoDebugComponent {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<button mat-button mat-raised-button class="footer-button" color="primary" (click)="takeOrChoosePicture()">
<span>
<mat-icon class="photo-icon">add_a_photo</mat-icon>
{{ 'Add Photo' | translate }}
</span>
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.photo-icon {
position: relative;
top: 2px;
right: 5px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Component, ElementRef, input, ViewChild } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import { Capacitor } from '@capacitor/core';

import { PicsaTranslateModule } from '../../../../modules/translate';
import { PhotoService } from '../../photo.service';
import { ENTRY_TEMPLATE } from '../../schema';

@Component({
selector: 'picsa-photo-input',
templateUrl: './photo-input.component.html',
styleUrls: ['./photo-input.component.scss'],
standalone: true,
imports: [PicsaTranslateModule, MatButtonModule, MatIconModule],
})
export class PhotoInputComponent {
@ViewChild('fileInput') fileInput: ElementRef;
/** Store photo within a specific named album */
album = input.required<string>();
/**
* Name to store photo as. If unspecified will be randomly generated
* If duplicate will override
*/
name = input<string>();

constructor(private photoService: PhotoService) {}

async ngOnInit() {
await this.photoService.init();
}

// this method will be called when a user clicks the camera button
async takeOrChoosePicture() {
const platform = Capacitor.getPlatform();
const source = platform === 'web' ? CameraSource.Photos : CameraSource.Prompt;

const image = await Camera.getPhoto({
quality: 90,
allowEditing: false,
resultType: CameraResultType.Uri,
source: source,
});

if (image.webPath) {
const fetchRes = await fetch(image.webPath);
const blob = await fetchRes.blob();
const entry = ENTRY_TEMPLATE(this.album(), this.name());
await this.photoService.savePhoto(entry, blob);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div class="photo-grid">
@for(photo of photos(); track photo.id;){
<picsa-photo-view [photo]="photo"></picsa-photo-view>
}
</div>
@if(photos().length ===0){
<div class="no-photos-message">{{ 'No photos added' | translate }}</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.photo-grid {
display: grid;
grid-auto-rows: 120px;
grid-template-columns: repeat(auto-fit, 120px);
gap: 8px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { PhotoListComponent } from './photo-list.component';

describe('PhotoListComponent', () => {
let component: PhotoListComponent;
let fixture: ComponentFixture<PhotoListComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PhotoListComponent],
}).compileComponents();

fixture = TestBed.createComponent(PhotoListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Component, computed, input } from '@angular/core';

import { PicsaTranslateModule } from '../../../../modules';
import { PhotoService } from '../../photo.service';
import { PhotoViewComponent } from '../photo-view/photo-view.component';

@Component({
selector: 'picsa-photo-list',
standalone: true,
imports: [PhotoViewComponent, PicsaTranslateModule],
templateUrl: './photo-list.component.html',
styleUrl: './photo-list.component.scss',
})
export class PhotoListComponent {
album = input<string>();

photos = computed(() => {
const album = this.album();
const photoDocs = this.service.photos();
return album ? photoDocs.filter((d) => d.album === album) : photoDocs;
});

constructor(private service: PhotoService) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@if(uri()){
<img [src]="uri()" />
<!-- TODO - show delete option on long press -->
<button mat-icon-button (click)="promptDelete()" color="secondary" class="delete-button">
<mat-icon>close</mat-icon>
</button>

} @if(errorMsg()){
<div class="error-msg">{{ errorMsg() }}</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
:host {
display: block;
position: relative;
}
img {
height: 100%;
width: 100%;
object-fit: cover;
}
button.delete-button.mat-mdc-icon-button.mat-mdc-button-base {
--mdc-icon-button-state-layer-size: 32px;
padding: 4px;
border-radius: 0;
position: absolute;
top: 0px;
right: 0px;
color: rgb(0 0 0 / 50%);
background: rgb(255 255 255 / 20%);
}
.error-msg {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
height: 100%;
border: 1px solid var(--color-light);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { PhotoViewComponent } from './photo-view.component';

describe('PhotoViewComponent', () => {
let component: PhotoViewComponent;
let fixture: ComponentFixture<PhotoViewComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PhotoViewComponent],
}).compileComponents();

fixture = TestBed.createComponent(PhotoViewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Loading

0 comments on commit 0dc1c09

Please sign in to comment.