diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json index 2f49d155..7d9198e3 100644 --- a/.fvm/fvm_config.json +++ b/.fvm/fvm_config.json @@ -1,4 +1,4 @@ { - "flutterSdkVersion": "3.10.6", + "flutterSdkVersion": "3.13.0", "flavors": {} } \ No newline at end of file diff --git a/.github/workflows/flutter_ci_test.yaml b/.github/workflows/flutter_ci_test.yaml index e219c112..3194d118 100644 --- a/.github/workflows/flutter_ci_test.yaml +++ b/.github/workflows/flutter_ci_test.yaml @@ -38,20 +38,20 @@ jobs: changed_files=$(git diff $base_sha $current_sha --name-only | grep '\.dart$' || true) if [[ -n "$changed_files" ]]; then echo "Dart files changed." - echo "dart-changes=true" >> $GITHUB_ENV + echo "::set-output name=run-tests::true" else echo "No Dart files changed." - echo "dart-changes=false" >> $GITHUB_ENV + echo "::set-output name=run-tests::false" fi # fvm のバージョンとチャネルを環境変数に設定する - name: Check fvm - if: env.run-tests == 'true' + if: steps.dartfile.outputs.run-tests == 'true' uses: kuhnroyal/flutter-fvm-config-action@v1 # Flutter SDK の設定 - name: Setup Flutter SDK - if: env.run-tests == 'true' + if: steps.dartfile.outputs.run-tests == 'true' uses: subosito/flutter-action@v2 with: # バージョンとチャネルは fvm の値を使う @@ -66,35 +66,35 @@ jobs: # melosの設定 # デフォルトでmelos bootstrapコマンドが実行される - name: Setup melos - if: env.run-tests == 'true' + if: steps.dartfile.outputs.run-tests == 'true' uses: bluefireteam/melos-action@v1 with: run-bootstrap: false # melos bootstrap を実行する。 - name: melos bootstrap - if: env.run-tests == 'true' + if: steps.dartfile.outputs.run-tests == 'true' run: melos bootstrap --sdk-path=${{ runner.tool_cache }}/flutter # 依存関係を解決する。 - name: Install Flutter dependencies by melos - if: env.run-tests == 'true' + if: steps.dartfile.outputs.run-tests == 'true' run: melos pg --sdk-path=${{ runner.tool_cache }}/flutter # コードフォーマットを実行する。 # フォーマットの結果変更が発生した場合はエラー扱いにする。 - name: Run Flutter format - if: env.run-tests == 'true' + if: steps.dartfile.outputs.run-tests == 'true' run: melos exec --sdk-path=${{ runner.tool_cache }}/flutter -- "dart format --set-exit-if-changed ." # 静的解析を実行する。 - name: Analyze project source - if: env.run-tests == 'true' + if: steps.dartfile.outputs.run-tests == 'true' run: melos exec --sdk-path=${{ runner.tool_cache }}/flutter -- "flutter analyze ." # テストを実行する。 - name: Run-Flutter-Test - if: env.run-tests == 'true' + if: steps.dartfile.outputs.run-tests == 'true' run: melos exec --sdk-path=${{ runner.tool_cache }}/flutter -- "flutter test --machine --coverage > test-report.log" # mottai_flutter_app のテスト結果を GitHub Actions に表示する。 @@ -102,7 +102,7 @@ jobs: uses: dorny/test-reporter@v1 # テスト結果を表示するのでテストが失敗しても実行する。 # ただし、コードフォーマットや静的解析で CI が失敗した場合は処理を行わない。 - if: env.run-tests == 'true' && steps.Run-Flutter-Test.outputs.return-code == '0' + if: steps.dartfile.outputs.run-tests == 'true' && steps.Run-Flutter-Test.outputs.return-code == '0' with: name: Flutter Test Report mottai_flutter_app path: /home/runner/work/mottai-flutter-app/mottai-flutter-app/packages/mottai_flutter_app/test-report.log @@ -110,7 +110,7 @@ jobs: # Codecov に結果を送信する。 - name: Upload coverage to Codecov - if: env.run-tests == 'true' + if: steps.dartfile.outputs.run-tests == 'true' uses: codecov/codecov-action@v2 with: token: ${{secrets.CODECOV_TOKEN}} @@ -124,7 +124,7 @@ jobs: uses: dorny/test-reporter@v1 # テスト結果を表示するのでテストが失敗しても実行する。 # ただし、コードフォーマットや静的解析で CI が失敗した場合は処理を行わない。 - if: env.run-tests == 'true' && steps.Run-Flutter-Test.outputs.return-code == '0' + if: steps.dartfile.outputs.run-tests == 'true' && steps.Run-Flutter-Test.outputs.return-code == '0' with: name: Flutter Test Report mottai_flutter_app path: /home/runner/work/mottai-flutter-app/mottai-flutter-app/packages/firebase_common/test-report.log @@ -132,7 +132,7 @@ jobs: # Codecov に結果を送信する。 - name: Upload coverage to Codecov - if: env.run-tests == 'true' + if: steps.dartfile.outputs.run-tests == 'true' uses: codecov/codecov-action@v2 with: token: ${{secrets.CODECOV_TOKEN}} @@ -146,7 +146,7 @@ jobs: uses: dorny/test-reporter@v1 # テスト結果を表示するのでテストが失敗しても実行する。 # ただし、コードフォーマットや静的解析でCIが失敗した場合は処理を行わない。 - if: env.run-tests == 'true' && steps.Run-Flutter-Test.outputs.return-code == '0' + if: steps.dartfile.outputs.run-tests == 'true' && steps.Run-Flutter-Test.outputs.return-code == '0' with: name: Flutter Test Report dart_flutter_common path: /home/runner/work/mottai-flutter-app/mottai-flutter-app/packages/dart_flutter_common/test-report.log @@ -154,7 +154,7 @@ jobs: # Codecov に結果を送信する。 - name: Upload coverage to Codecov - if: env.run-tests == 'true' + if: steps.dartfile.outputs.run-tests == 'true' uses: codecov/codecov-action@v2 with: token: ${{secrets.CODECOV_TOKEN}} diff --git a/functions/src/development/disableUserAccount/onCreateDisableUserAccountRequest.ts b/functions/src/development/disableUserAccount/onCreateDisableUserAccountRequest.ts new file mode 100644 index 00000000..67b2b13b --- /dev/null +++ b/functions/src/development/disableUserAccount/onCreateDisableUserAccountRequest.ts @@ -0,0 +1,19 @@ +import * as admin from 'firebase-admin' +import * as functions from 'firebase-functions' + +/** + * 新しい disableUserAccountRequest ドキュメントが作成されたときに発火する。 + * disableUserAccountRequest を作成したユーザーを disable にする。 + */ +export const onCreateDisableUserAccountRequest = functions + .region(`asia-northeast1`) + .firestore.document(`/disableUserAccountRequests/{disableUserAccountRequest}`) + .onCreate(async (snapshot) => { + const disableUserAccountRequest = snapshot.data() + const userId = disableUserAccountRequest.userId + try { + await admin.auth().updateUser(userId, { disabled: true }); + } catch (e) { + functions.logger.error(`退会処理に失敗しました。${e}`) + } + }) diff --git a/functions/src/index.ts b/functions/src/index.ts index 2f18fb4a..d8f06849 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -30,6 +30,7 @@ functions.setGlobalOptions({ region: `asia-northeast1` }) // /** ここでデプロイする関数をまとめる。 */ import { createfirebaseauthcustomtoken } from './callable-functions/createFirebaseAuthCustomToken' import { onCreateTestNotification } from './development/testNotification/onCreateTestNotification' +import { onCreateDisableUserAccountRequest } from './development/disableUserAccount/onCreateDisableUserAccountRequest' // /** index.ts で import してデプロイする関数一覧。 */ -export { createfirebaseauthcustomtoken, onCreateTestNotification } +export { createfirebaseauthcustomtoken, onCreateTestNotification, onCreateDisableUserAccountRequest } diff --git a/packages/dart_flutter_common/pubspec.lock b/packages/dart_flutter_common/pubspec.lock index 6547ccd7..6eb89885 100644 --- a/packages/dart_flutter_common/pubspec.lock +++ b/packages/dart_flutter_common/pubspec.lock @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" cross_file: dependency: transitive description: @@ -292,18 +292,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -433,10 +433,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" sqflite: dependency: transitive description: @@ -497,10 +497,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" typed_data: dependency: transitive description: @@ -589,6 +589,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" win32: dependency: transitive description: @@ -606,5 +614,5 @@ packages: source: hosted version: "1.0.0" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0" diff --git a/packages/firebase_common/lib/src/firestore_documents/_export.dart b/packages/firebase_common/lib/src/firestore_documents/_export.dart index 41d166e7..2c92e15d 100644 --- a/packages/firebase_common/lib/src/firestore_documents/_export.dart +++ b/packages/firebase_common/lib/src/firestore_documents/_export.dart @@ -1,5 +1,6 @@ export 'chat_message.dart'; export 'chat_room.dart'; +export 'disable_user_account_request.dart'; export 'force_update_config.dart'; export 'host.dart'; export 'host_location.dart'; diff --git a/packages/firebase_common/lib/src/firestore_documents/disable_user_account_request.dart b/packages/firebase_common/lib/src/firestore_documents/disable_user_account_request.dart new file mode 100644 index 00000000..2b1540a5 --- /dev/null +++ b/packages/firebase_common/lib/src/firestore_documents/disable_user_account_request.dart @@ -0,0 +1,20 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutterfire_gen_annotation/flutterfire_gen_annotation.dart'; + +part 'disable_user_account_request.flutterfire_gen.dart'; + +@FirestoreDocument( + path: 'disableUserAccountRequests', + documentName: 'disableUserAccountRequest', +) +class DisableUserAccountRequest { + const DisableUserAccountRequest({ + required this.userId, + this.createdAt, + }); + + final String userId; + + @AlwaysUseFieldValueServerTimestampWhenCreating() + final DateTime? createdAt; +} diff --git a/packages/firebase_common/lib/src/firestore_documents/disable_user_account_request.flutterfire_gen.dart b/packages/firebase_common/lib/src/firestore_documents/disable_user_account_request.flutterfire_gen.dart new file mode 100644 index 00000000..43c25b16 --- /dev/null +++ b/packages/firebase_common/lib/src/firestore_documents/disable_user_account_request.flutterfire_gen.dart @@ -0,0 +1,261 @@ +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'disable_user_account_request.dart'; + +class ReadDisableUserAccountRequest { + const ReadDisableUserAccountRequest({ + required this.disableUserAccountRequestId, + required this.path, + required this.userId, + required this.createdAt, + }); + + final String disableUserAccountRequestId; + + final String path; + + final String userId; + + final DateTime? createdAt; + + factory ReadDisableUserAccountRequest._fromJson(Map json) { + return ReadDisableUserAccountRequest( + disableUserAccountRequestId: + json['disableUserAccountRequestId'] as String, + path: json['path'] as String, + userId: json['userId'] as String, + createdAt: (json['createdAt'] as Timestamp?)?.toDate(), + ); + } + + factory ReadDisableUserAccountRequest.fromDocumentSnapshot( + DocumentSnapshot ds) { + final data = ds.data()! as Map; + return ReadDisableUserAccountRequest._fromJson({ + ...data, + 'disableUserAccountRequestId': ds.id, + 'path': ds.reference.path, + }); + } +} + +class CreateDisableUserAccountRequest { + const CreateDisableUserAccountRequest({ + required this.userId, + }); + + final String userId; + + Map toJson() { + return { + 'userId': userId, + 'createdAt': FieldValue.serverTimestamp(), + }; + } +} + +class UpdateDisableUserAccountRequest { + const UpdateDisableUserAccountRequest({ + this.userId, + this.createdAt, + }); + + final String? userId; + final DateTime? createdAt; + + Map toJson() { + return { + if (userId != null) 'userId': userId, + if (createdAt != null) 'createdAt': createdAt, + }; + } +} + +class DeleteDisableUserAccountRequest {} + +/// Provides a reference to the disableUserAccountRequests collection for reading. +final readDisableUserAccountRequestCollectionReference = FirebaseFirestore + .instance + .collection('disableUserAccountRequests') + .withConverter( + fromFirestore: (ds, _) => + ReadDisableUserAccountRequest.fromDocumentSnapshot(ds), + toFirestore: (_, __) => throw UnimplementedError(), + ); + +/// Provides a reference to a disableUserAccountRequest document for reading. +DocumentReference + readDisableUserAccountRequestDocumentReference({ + required String disableUserAccountRequestId, +}) => + readDisableUserAccountRequestCollectionReference + .doc(disableUserAccountRequestId); + +/// Provides a reference to the disableUserAccountRequests collection for creating. +final createDisableUserAccountRequestCollectionReference = FirebaseFirestore + .instance + .collection('disableUserAccountRequests') + .withConverter( + fromFirestore: (_, __) => throw UnimplementedError(), + toFirestore: (obj, _) => obj.toJson(), + ); + +/// Provides a reference to a disableUserAccountRequest document for creating. +DocumentReference + createDisableUserAccountRequestDocumentReference({ + required String disableUserAccountRequestId, +}) => + createDisableUserAccountRequestCollectionReference + .doc(disableUserAccountRequestId); + +/// Provides a reference to the disableUserAccountRequests collection for updating. +final updateDisableUserAccountRequestCollectionReference = FirebaseFirestore + .instance + .collection('disableUserAccountRequests') + .withConverter( + fromFirestore: (_, __) => throw UnimplementedError(), + toFirestore: (obj, _) => obj.toJson(), + ); + +/// Provides a reference to a disableUserAccountRequest document for updating. +DocumentReference + updateDisableUserAccountRequestDocumentReference({ + required String disableUserAccountRequestId, +}) => + updateDisableUserAccountRequestCollectionReference + .doc(disableUserAccountRequestId); + +/// Provides a reference to the disableUserAccountRequests collection for deleting. +final deleteDisableUserAccountRequestCollectionReference = FirebaseFirestore + .instance + .collection('disableUserAccountRequests') + .withConverter( + fromFirestore: (_, __) => throw UnimplementedError(), + toFirestore: (_, __) => throw UnimplementedError(), + ); + +/// Provides a reference to a disableUserAccountRequest document for deleting. +DocumentReference + deleteDisableUserAccountRequestDocumentReference({ + required String disableUserAccountRequestId, +}) => + deleteDisableUserAccountRequestCollectionReference + .doc(disableUserAccountRequestId); + +/// Manages queries against the disableUserAccountRequests collection. +class DisableUserAccountRequestQuery { + /// Fetches [ReadDisableUserAccountRequest] documents. + Future> fetchDocuments({ + GetOptions? options, + Query? Function( + Query query)? + queryBuilder, + int Function(ReadDisableUserAccountRequest lhs, + ReadDisableUserAccountRequest rhs)? + compare, + }) async { + Query query = + readDisableUserAccountRequestCollectionReference; + if (queryBuilder != null) { + query = queryBuilder(query)!; + } + final qs = await query.get(options); + final result = qs.docs.map((qds) => qds.data()).toList(); + if (compare != null) { + result.sort(compare); + } + return result; + } + + /// Subscribes [DisableUserAccountRequest] documents. + Stream> subscribeDocuments({ + Query? Function( + Query query)? + queryBuilder, + int Function(ReadDisableUserAccountRequest lhs, + ReadDisableUserAccountRequest rhs)? + compare, + bool includeMetadataChanges = false, + bool excludePendingWrites = false, + }) { + Query query = + readDisableUserAccountRequestCollectionReference; + if (queryBuilder != null) { + query = queryBuilder(query)!; + } + var streamQs = + query.snapshots(includeMetadataChanges: includeMetadataChanges); + if (excludePendingWrites) { + streamQs = streamQs.where((qs) => !qs.metadata.hasPendingWrites); + } + return streamQs.map((qs) { + final result = qs.docs.map((qds) => qds.data()).toList(); + if (compare != null) { + result.sort(compare); + } + return result; + }); + } + + /// Fetches a specific [ReadDisableUserAccountRequest] document. + Future fetchDocument({ + required String disableUserAccountRequestId, + GetOptions? options, + }) async { + final ds = await readDisableUserAccountRequestDocumentReference( + disableUserAccountRequestId: disableUserAccountRequestId, + ).get(options); + return ds.data(); + } + + /// Subscribes a specific [DisableUserAccountRequest] document. + Stream subscribeDocument({ + required String disableUserAccountRequestId, + bool includeMetadataChanges = false, + bool excludePendingWrites = false, + }) { + var streamDs = readDisableUserAccountRequestDocumentReference( + disableUserAccountRequestId: disableUserAccountRequestId, + ).snapshots(includeMetadataChanges: includeMetadataChanges); + if (excludePendingWrites) { + streamDs = streamDs.where((ds) => !ds.metadata.hasPendingWrites); + } + return streamDs.map((ds) => ds.data()); + } + + /// Adds a [DisableUserAccountRequest] document. + Future> add({ + required CreateDisableUserAccountRequest createDisableUserAccountRequest, + }) => + createDisableUserAccountRequestCollectionReference + .add(createDisableUserAccountRequest); + + /// Sets a [DisableUserAccountRequest] document. + Future set({ + required String disableUserAccountRequestId, + required CreateDisableUserAccountRequest createDisableUserAccountRequest, + SetOptions? options, + }) => + createDisableUserAccountRequestDocumentReference( + disableUserAccountRequestId: disableUserAccountRequestId, + ).set(createDisableUserAccountRequest, options); + + /// Updates a specific [DisableUserAccountRequest] document. + Future update({ + required String disableUserAccountRequestId, + required UpdateDisableUserAccountRequest updateDisableUserAccountRequest, + }) => + updateDisableUserAccountRequestDocumentReference( + disableUserAccountRequestId: disableUserAccountRequestId, + ).update(updateDisableUserAccountRequest.toJson()); + + /// Deletes a specific [DisableUserAccountRequest] document. + Future delete({ + required String disableUserAccountRequestId, + }) => + deleteDisableUserAccountRequestDocumentReference( + disableUserAccountRequestId: disableUserAccountRequestId, + ).delete(); +} diff --git a/packages/firebase_common/lib/src/firestore_documents/user_blocked_document.dart b/packages/firebase_common/lib/src/firestore_documents/user_blocked_document.dart new file mode 100644 index 00000000..1981adfc --- /dev/null +++ b/packages/firebase_common/lib/src/firestore_documents/user_blocked_document.dart @@ -0,0 +1,30 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutterfire_gen_annotation/flutterfire_gen_annotation.dart'; + +part 'user_blocked_document.flutterfire_gen.dart'; + +@FirestoreDocument( + path: 'userBlockedDocuments/{userId}/jobs', + documentName: 'job', +) +class BlockedJob { + const BlockedJob({ + this.createdAt, + }); + + @AlwaysUseFieldValueServerTimestampWhenCreating() + final DateTime? createdAt; +} + +@FirestoreDocument( + path: 'userBlockedDocuments/{userId}/reviews', + documentName: 'review', +) +class BlockedReview { + const BlockedReview({ + this.createdAt, + }); + + @AlwaysUseFieldValueServerTimestampWhenCreating() + final DateTime? createdAt; +} diff --git a/packages/firebase_common/lib/src/firestore_documents/user_blocked_document.flutterfire_gen.dart b/packages/firebase_common/lib/src/firestore_documents/user_blocked_document.flutterfire_gen.dart new file mode 100644 index 00000000..64647608 --- /dev/null +++ b/packages/firebase_common/lib/src/firestore_documents/user_blocked_document.flutterfire_gen.dart @@ -0,0 +1,525 @@ +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'user_blocked_document.dart'; + +class ReadBlockedJob { + const ReadBlockedJob({ + required this.jobId, + required this.path, + required this.createdAt, + }); + + final String jobId; + + final String path; + + final DateTime? createdAt; + + factory ReadBlockedJob._fromJson(Map json) { + return ReadBlockedJob( + jobId: json['jobId'] as String, + path: json['path'] as String, + createdAt: (json['createdAt'] as Timestamp?)?.toDate(), + ); + } + + factory ReadBlockedJob.fromDocumentSnapshot(DocumentSnapshot ds) { + final data = ds.data()! as Map; + return ReadBlockedJob._fromJson({ + ...data, + 'jobId': ds.id, + 'path': ds.reference.path, + }); + } +} + +class CreateBlockedJob { + const CreateBlockedJob(); + + Map toJson() { + return { + 'createdAt': FieldValue.serverTimestamp(), + }; + } +} + +class UpdateBlockedJob { + const UpdateBlockedJob({ + this.createdAt, + }); + + final DateTime? createdAt; + + Map toJson() { + return { + if (createdAt != null) 'createdAt': createdAt, + }; + } +} + +class DeleteBlockedJob {} + +/// Provides a reference to the jobs collection for reading. +CollectionReference readBlockedJobCollectionReference({ + required String userId, +}) => + FirebaseFirestore.instance + .collection('userBlockedDocuments') + .doc(userId) + .collection('jobs') + .withConverter( + fromFirestore: (ds, _) => ReadBlockedJob.fromDocumentSnapshot(ds), + toFirestore: (_, __) => throw UnimplementedError(), + ); + +/// Provides a reference to a job document for reading. +DocumentReference readBlockedJobDocumentReference({ + required String userId, + required String jobId, +}) => + readBlockedJobCollectionReference(userId: userId).doc(jobId); + +/// Provides a reference to the jobs collection for creating. +CollectionReference createBlockedJobCollectionReference({ + required String userId, +}) => + FirebaseFirestore.instance + .collection('userBlockedDocuments') + .doc(userId) + .collection('jobs') + .withConverter( + fromFirestore: (_, __) => throw UnimplementedError(), + toFirestore: (obj, _) => obj.toJson(), + ); + +/// Provides a reference to a job document for creating. +DocumentReference createBlockedJobDocumentReference({ + required String userId, + required String jobId, +}) => + createBlockedJobCollectionReference(userId: userId).doc(jobId); + +/// Provides a reference to the jobs collection for updating. +CollectionReference updateBlockedJobCollectionReference({ + required String userId, +}) => + FirebaseFirestore.instance + .collection('userBlockedDocuments') + .doc(userId) + .collection('jobs') + .withConverter( + fromFirestore: (_, __) => throw UnimplementedError(), + toFirestore: (obj, _) => obj.toJson(), + ); + +/// Provides a reference to a job document for updating. +DocumentReference updateBlockedJobDocumentReference({ + required String userId, + required String jobId, +}) => + updateBlockedJobCollectionReference(userId: userId).doc(jobId); + +/// Provides a reference to the jobs collection for deleting. +CollectionReference deleteBlockedJobCollectionReference({ + required String userId, +}) => + FirebaseFirestore.instance + .collection('userBlockedDocuments') + .doc(userId) + .collection('jobs') + .withConverter( + fromFirestore: (_, __) => throw UnimplementedError(), + toFirestore: (_, __) => throw UnimplementedError(), + ); + +/// Provides a reference to a job document for deleting. +DocumentReference deleteBlockedJobDocumentReference({ + required String userId, + required String jobId, +}) => + deleteBlockedJobCollectionReference(userId: userId).doc(jobId); + +/// Manages queries against the jobs collection. +class BlockedJobQuery { + /// Fetches [ReadBlockedJob] documents. + Future> fetchDocuments({ + required String userId, + GetOptions? options, + Query? Function(Query query)? queryBuilder, + int Function(ReadBlockedJob lhs, ReadBlockedJob rhs)? compare, + }) async { + Query query = + readBlockedJobCollectionReference(userId: userId); + if (queryBuilder != null) { + query = queryBuilder(query)!; + } + final qs = await query.get(options); + final result = qs.docs.map((qds) => qds.data()).toList(); + if (compare != null) { + result.sort(compare); + } + return result; + } + + /// Subscribes [BlockedJob] documents. + Stream> subscribeDocuments({ + required String userId, + Query? Function(Query query)? queryBuilder, + int Function(ReadBlockedJob lhs, ReadBlockedJob rhs)? compare, + bool includeMetadataChanges = false, + bool excludePendingWrites = false, + }) { + Query query = + readBlockedJobCollectionReference(userId: userId); + if (queryBuilder != null) { + query = queryBuilder(query)!; + } + var streamQs = + query.snapshots(includeMetadataChanges: includeMetadataChanges); + if (excludePendingWrites) { + streamQs = streamQs.where((qs) => !qs.metadata.hasPendingWrites); + } + return streamQs.map((qs) { + final result = qs.docs.map((qds) => qds.data()).toList(); + if (compare != null) { + result.sort(compare); + } + return result; + }); + } + + /// Fetches a specific [ReadBlockedJob] document. + Future fetchDocument({ + required String userId, + required String jobId, + GetOptions? options, + }) async { + final ds = await readBlockedJobDocumentReference( + userId: userId, + jobId: jobId, + ).get(options); + return ds.data(); + } + + /// Subscribes a specific [BlockedJob] document. + Stream subscribeDocument({ + required String userId, + required String jobId, + bool includeMetadataChanges = false, + bool excludePendingWrites = false, + }) { + var streamDs = readBlockedJobDocumentReference( + userId: userId, + jobId: jobId, + ).snapshots(includeMetadataChanges: includeMetadataChanges); + if (excludePendingWrites) { + streamDs = streamDs.where((ds) => !ds.metadata.hasPendingWrites); + } + return streamDs.map((ds) => ds.data()); + } + + /// Adds a [BlockedJob] document. + Future> add({ + required String userId, + required CreateBlockedJob createBlockedJob, + }) => + createBlockedJobCollectionReference(userId: userId).add(createBlockedJob); + + /// Sets a [BlockedJob] document. + Future set({ + required String userId, + required String jobId, + required CreateBlockedJob createBlockedJob, + SetOptions? options, + }) => + createBlockedJobDocumentReference( + userId: userId, + jobId: jobId, + ).set(createBlockedJob, options); + + /// Updates a specific [BlockedJob] document. + Future update({ + required String userId, + required String jobId, + required UpdateBlockedJob updateBlockedJob, + }) => + updateBlockedJobDocumentReference( + userId: userId, + jobId: jobId, + ).update(updateBlockedJob.toJson()); + + /// Deletes a specific [BlockedJob] document. + Future delete({ + required String userId, + required String jobId, + }) => + deleteBlockedJobDocumentReference( + userId: userId, + jobId: jobId, + ).delete(); +} + +class ReadBlockedReview { + const ReadBlockedReview({ + required this.reviewId, + required this.path, + required this.createdAt, + }); + + final String reviewId; + + final String path; + + final DateTime? createdAt; + + factory ReadBlockedReview._fromJson(Map json) { + return ReadBlockedReview( + reviewId: json['reviewId'] as String, + path: json['path'] as String, + createdAt: (json['createdAt'] as Timestamp?)?.toDate(), + ); + } + + factory ReadBlockedReview.fromDocumentSnapshot(DocumentSnapshot ds) { + final data = ds.data()! as Map; + return ReadBlockedReview._fromJson({ + ...data, + 'reviewId': ds.id, + 'path': ds.reference.path, + }); + } +} + +class CreateBlockedReview { + const CreateBlockedReview(); + + Map toJson() { + return { + 'createdAt': FieldValue.serverTimestamp(), + }; + } +} + +class UpdateBlockedReview { + const UpdateBlockedReview({ + this.createdAt, + }); + + final DateTime? createdAt; + + Map toJson() { + return { + if (createdAt != null) 'createdAt': createdAt, + }; + } +} + +class DeleteBlockedReview {} + +/// Provides a reference to the reviews collection for reading. +CollectionReference readBlockedReviewCollectionReference({ + required String userId, +}) => + FirebaseFirestore.instance + .collection('userBlockedDocuments') + .doc(userId) + .collection('reviews') + .withConverter( + fromFirestore: (ds, _) => ReadBlockedReview.fromDocumentSnapshot(ds), + toFirestore: (_, __) => throw UnimplementedError(), + ); + +/// Provides a reference to a review document for reading. +DocumentReference readBlockedReviewDocumentReference({ + required String userId, + required String reviewId, +}) => + readBlockedReviewCollectionReference(userId: userId).doc(reviewId); + +/// Provides a reference to the reviews collection for creating. +CollectionReference + createBlockedReviewCollectionReference({ + required String userId, +}) => + FirebaseFirestore.instance + .collection('userBlockedDocuments') + .doc(userId) + .collection('reviews') + .withConverter( + fromFirestore: (_, __) => throw UnimplementedError(), + toFirestore: (obj, _) => obj.toJson(), + ); + +/// Provides a reference to a review document for creating. +DocumentReference createBlockedReviewDocumentReference({ + required String userId, + required String reviewId, +}) => + createBlockedReviewCollectionReference(userId: userId).doc(reviewId); + +/// Provides a reference to the reviews collection for updating. +CollectionReference + updateBlockedReviewCollectionReference({ + required String userId, +}) => + FirebaseFirestore.instance + .collection('userBlockedDocuments') + .doc(userId) + .collection('reviews') + .withConverter( + fromFirestore: (_, __) => throw UnimplementedError(), + toFirestore: (obj, _) => obj.toJson(), + ); + +/// Provides a reference to a review document for updating. +DocumentReference updateBlockedReviewDocumentReference({ + required String userId, + required String reviewId, +}) => + updateBlockedReviewCollectionReference(userId: userId).doc(reviewId); + +/// Provides a reference to the reviews collection for deleting. +CollectionReference + deleteBlockedReviewCollectionReference({ + required String userId, +}) => + FirebaseFirestore.instance + .collection('userBlockedDocuments') + .doc(userId) + .collection('reviews') + .withConverter( + fromFirestore: (_, __) => throw UnimplementedError(), + toFirestore: (_, __) => throw UnimplementedError(), + ); + +/// Provides a reference to a review document for deleting. +DocumentReference deleteBlockedReviewDocumentReference({ + required String userId, + required String reviewId, +}) => + deleteBlockedReviewCollectionReference(userId: userId).doc(reviewId); + +/// Manages queries against the reviews collection. +class BlockedReviewQuery { + /// Fetches [ReadBlockedReview] documents. + Future> fetchDocuments({ + required String userId, + GetOptions? options, + Query? Function(Query query)? + queryBuilder, + int Function(ReadBlockedReview lhs, ReadBlockedReview rhs)? compare, + }) async { + Query query = + readBlockedReviewCollectionReference(userId: userId); + if (queryBuilder != null) { + query = queryBuilder(query)!; + } + final qs = await query.get(options); + final result = qs.docs.map((qds) => qds.data()).toList(); + if (compare != null) { + result.sort(compare); + } + return result; + } + + /// Subscribes [BlockedReview] documents. + Stream> subscribeDocuments({ + required String userId, + Query? Function(Query query)? + queryBuilder, + int Function(ReadBlockedReview lhs, ReadBlockedReview rhs)? compare, + bool includeMetadataChanges = false, + bool excludePendingWrites = false, + }) { + Query query = + readBlockedReviewCollectionReference(userId: userId); + if (queryBuilder != null) { + query = queryBuilder(query)!; + } + var streamQs = + query.snapshots(includeMetadataChanges: includeMetadataChanges); + if (excludePendingWrites) { + streamQs = streamQs.where((qs) => !qs.metadata.hasPendingWrites); + } + return streamQs.map((qs) { + final result = qs.docs.map((qds) => qds.data()).toList(); + if (compare != null) { + result.sort(compare); + } + return result; + }); + } + + /// Fetches a specific [ReadBlockedReview] document. + Future fetchDocument({ + required String userId, + required String reviewId, + GetOptions? options, + }) async { + final ds = await readBlockedReviewDocumentReference( + userId: userId, + reviewId: reviewId, + ).get(options); + return ds.data(); + } + + /// Subscribes a specific [BlockedReview] document. + Stream subscribeDocument({ + required String userId, + required String reviewId, + bool includeMetadataChanges = false, + bool excludePendingWrites = false, + }) { + var streamDs = readBlockedReviewDocumentReference( + userId: userId, + reviewId: reviewId, + ).snapshots(includeMetadataChanges: includeMetadataChanges); + if (excludePendingWrites) { + streamDs = streamDs.where((ds) => !ds.metadata.hasPendingWrites); + } + return streamDs.map((ds) => ds.data()); + } + + /// Adds a [BlockedReview] document. + Future> add({ + required String userId, + required CreateBlockedReview createBlockedReview, + }) => + createBlockedReviewCollectionReference(userId: userId) + .add(createBlockedReview); + + /// Sets a [BlockedReview] document. + Future set({ + required String userId, + required String reviewId, + required CreateBlockedReview createBlockedReview, + SetOptions? options, + }) => + createBlockedReviewDocumentReference( + userId: userId, + reviewId: reviewId, + ).set(createBlockedReview, options); + + /// Updates a specific [BlockedReview] document. + Future update({ + required String userId, + required String reviewId, + required UpdateBlockedReview updateBlockedReview, + }) => + updateBlockedReviewDocumentReference( + userId: userId, + reviewId: reviewId, + ).update(updateBlockedReview.toJson()); + + /// Deletes a specific [BlockedReview] document. + Future delete({ + required String userId, + required String reviewId, + }) => + deleteBlockedReviewDocumentReference( + userId: userId, + reviewId: reviewId, + ).delete(); +} diff --git a/packages/firebase_common/lib/src/firestore_repositories/_export.dart b/packages/firebase_common/lib/src/firestore_repositories/_export.dart index 41d166e7..66dcc60c 100644 --- a/packages/firebase_common/lib/src/firestore_repositories/_export.dart +++ b/packages/firebase_common/lib/src/firestore_repositories/_export.dart @@ -1,5 +1,6 @@ export 'chat_message.dart'; export 'chat_room.dart'; +export 'disable_user_account_request.dart'; export 'force_update_config.dart'; export 'host.dart'; export 'host_location.dart'; @@ -8,6 +9,7 @@ export 'job.dart'; export 'read_status.dart'; export 'review.dart'; export 'todo.dart'; +export 'user_blocked_document.dart'; export 'user_fcm_token.dart'; export 'user_social_login.dart'; export 'worker.dart'; diff --git a/packages/firebase_common/lib/src/firestore_repositories/disable_user_account_request.dart b/packages/firebase_common/lib/src/firestore_repositories/disable_user_account_request.dart new file mode 100644 index 00000000..accb43b9 --- /dev/null +++ b/packages/firebase_common/lib/src/firestore_repositories/disable_user_account_request.dart @@ -0,0 +1,16 @@ +import '../firestore_documents/disable_user_account_request.dart'; + +class DisableUserAccountRequestRepository { + final _query = DisableUserAccountRequestQuery(); + + /// [DisableUserAccountRequest] を作成する。 + Future setDisableUserAccountRequest({ + required String userId, + }) => + _query.set( + disableUserAccountRequestId: userId, + createDisableUserAccountRequest: CreateDisableUserAccountRequest( + userId: userId, + ), + ); +} diff --git a/packages/firebase_common/lib/src/firestore_repositories/user_blocked_document.dart b/packages/firebase_common/lib/src/firestore_repositories/user_blocked_document.dart new file mode 100644 index 00000000..908c5de4 --- /dev/null +++ b/packages/firebase_common/lib/src/firestore_repositories/user_blocked_document.dart @@ -0,0 +1,31 @@ +import '../firestore_documents/user_blocked_document.dart'; + +class BlockedJobRepository { + final _query = BlockedJobQuery(); + + /// [BlockedJob] の情報を作成する。 + Future create({ + required String userId, + required String jobId, + }) => + _query.set( + userId: userId, + jobId: jobId, + createBlockedJob: const CreateBlockedJob(), + ); +} + +class BlockedReviewRepository { + final _query = BlockedReviewQuery(); + + /// [BlockedReview] の情報を作成する。 + Future create({ + required String userId, + required String reviewId, + }) => + _query.set( + userId: userId, + reviewId: reviewId, + createBlockedReview: const CreateBlockedReview(), + ); +} diff --git a/packages/firebase_common/pubspec.lock b/packages/firebase_common/pubspec.lock index c0e53f27..cd066822 100644 --- a/packages/firebase_common/pubspec.lock +++ b/packages/firebase_common/pubspec.lock @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -396,18 +396,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -505,10 +505,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -553,10 +553,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" timing: dependency: transitive description: @@ -589,6 +589,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -606,5 +614,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0" diff --git a/packages/mottai_flutter_app/lib/auth/ui/auth_controller.dart b/packages/mottai_flutter_app/lib/auth/ui/auth_controller.dart index 869154e3..6632ceef 100644 --- a/packages/mottai_flutter_app/lib/auth/ui/auth_controller.dart +++ b/packages/mottai_flutter_app/lib/auth/ui/auth_controller.dart @@ -62,6 +62,12 @@ class AuthController { _appScaffoldMessengerController.showSnackBar('サインインしました'); } on AppException catch (e) { _appScaffoldMessengerController.showSnackBarByException(e); + } on FirebaseAuthException catch (e) { + if (e.code == 'user-disabled') { + _appScaffoldMessengerController.showSnackBar('このアカウントは退会済みのため無効です。'); + } else { + _appScaffoldMessengerController.showSnackBarByFirebaseException(e); + } } finally { _overlayLoadingStateController.update((state) => false); } diff --git a/packages/mottai_flutter_app/lib/auth/ui/button_builder.dart b/packages/mottai_flutter_app/lib/auth/ui/button_builder.dart index 9ae1d45a..f627f2d9 100644 --- a/packages/mottai_flutter_app/lib/auth/ui/button_builder.dart +++ b/packages/mottai_flutter_app/lib/auth/ui/button_builder.dart @@ -134,7 +134,7 @@ class SignInButtonBuilder extends StatelessWidget { separator!, SizedBox( width: separatorSpaceRight, - ) + ), ], Text( text, diff --git a/packages/mottai_flutter_app/lib/block/block.dart b/packages/mottai_flutter_app/lib/block/block.dart new file mode 100644 index 00000000..2e59095c --- /dev/null +++ b/packages/mottai_flutter_app/lib/block/block.dart @@ -0,0 +1,46 @@ +import 'package:firebase_common/firebase_common.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../firestore_repository.dart'; + +final blockJobServiceProvider = Provider.autoDispose( + (ref) => BlockJobService( + blockJobRepository: ref.watch(blockedJobRepositoryProvider), + ), +); + +final blockReviewServiceProvider = Provider.autoDispose( + (ref) => BlockReviewService( + blockReviewRepository: ref.watch(blockedReviewRepositoryProvider), + ), +); + +class BlockJobService { + const BlockJobService({ + required BlockedJobRepository blockJobRepository, + }) : _blockJobRepository = blockJobRepository; + + final BlockedJobRepository _blockJobRepository; + + /// 指定したユーザー ([userId]) が、指定したお手伝い情報 ([jobId]) をブロックする。 + Future create({ + required String userId, + required String jobId, + }) => + _blockJobRepository.create(userId: userId, jobId: jobId); +} + +class BlockReviewService { + const BlockReviewService({ + required BlockedReviewRepository blockReviewRepository, + }) : _blockReviewRepository = blockReviewRepository; + + final BlockedReviewRepository _blockReviewRepository; + + /// 指定したユーザー ([userId]) が、指定したレビュー ([reviewId]) をブロックする。 + Future create({ + required String userId, + required String reviewId, + }) => + _blockReviewRepository.create(userId: userId, reviewId: reviewId); +} diff --git a/packages/mottai_flutter_app/lib/chat/chat_room.dart b/packages/mottai_flutter_app/lib/chat/chat_room.dart index 3e6c4736..43243021 100644 --- a/packages/mottai_flutter_app/lib/chat/chat_room.dart +++ b/packages/mottai_flutter_app/lib/chat/chat_room.dart @@ -173,7 +173,7 @@ class ChatRoomStateNotifier extends StateNotifier { state = state.copyWith( readChatMessages: [ ...state.newReadChatMessages, - ...state.pastReadChatMessages + ...state.pastReadChatMessages, ], ); } diff --git a/packages/mottai_flutter_app/lib/development/development_items/ui/development_items.dart b/packages/mottai_flutter_app/lib/development/development_items/ui/development_items.dart index d2d63747..9f1396da 100644 --- a/packages/mottai_flutter_app/lib/development/development_items/ui/development_items.dart +++ b/packages/mottai_flutter_app/lib/development/development_items/ui/development_items.dart @@ -17,7 +17,6 @@ import '../../../worker/ui/worker.dart'; import '../../color/ui/color.dart'; import '../../firebase_messaging/ui/firebase_messaging.dart'; import '../../firebase_storage/ui/firebase_storage.dart'; -import '../../force_update/ui/force_update.dart'; import '../../generic_image/ui/generic_images.dart'; import '../../geoflutterfire_plus/geoflutterfire_plus.dart'; import '../../image_detail_view/ui/image_detail_view_stub.dart'; @@ -26,6 +25,7 @@ import '../../in_review/ui/in_review.dart'; import '../../sample_todo/ui/todos.dart'; import '../../sign_in/ui/sign_in.dart'; import '../../user_fcm_token/ui/user_fcm_token.dart'; +import '../../user_generate_content/ui/user_generate_content_sample.dart'; import '../../user_social_login/user_social_login.dart'; import '../../web_link/ui/web_link_stub.dart'; @@ -156,11 +156,6 @@ class DevelopmentItemsPage extends ConsumerWidget { onTap: () => context.router.pushNamed(FirebaseStorageSamplePage.location), ), - ListTile( - title: const Text('強制アップデート'), - onTap: () => - context.router.pushNamed(ForceUpdateSamplePage.location), - ), ListTile( title: const Text('レビュー中かどうか'), onTap: () => context.router.pushNamed(InReviewPage.location), @@ -212,7 +207,10 @@ class DevelopmentItemsPage extends ConsumerWidget { ), const ListTile(title: Text('Security Rules')), const ListTile(title: Text('お問い合わせ')), - const ListTile(title: Text('不適切 UGC の通報 or 非表示')), + ListTile( + title: const Text('不適切 UGC の通報 or 非表示'), + onTap: () => context.router.pushNamed(UgcSamplePage.location), + ), const Divider(), Padding( padding: const EdgeInsets.all(16), diff --git a/packages/mottai_flutter_app/lib/development/disable_user_account_request/disable_user_account_request.dart b/packages/mottai_flutter_app/lib/development/disable_user_account_request/disable_user_account_request.dart new file mode 100644 index 00000000..5f03cc94 --- /dev/null +++ b/packages/mottai_flutter_app/lib/development/disable_user_account_request/disable_user_account_request.dart @@ -0,0 +1,46 @@ +import 'package:firebase_common/firebase_common.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../../auth/auth.dart'; +import '../../firestore_repository.dart'; + +final disableUserAccountRequestServiceProvider = + Provider.autoDispose( + (ref) => DisableUserAccountRequestService( + disableUserAccountRequestRepository: ref.watch( + disableUserAccountRequestRepositoryProvider, + ), + authService: ref.watch(authServiceProvider), + ), +); + +class DisableUserAccountRequestService { + const DisableUserAccountRequestService({ + required DisableUserAccountRequestRepository + disableUserAccountRequestRepository, + required AuthService authService, + }) : _disableUserAccountRequestRepository = + disableUserAccountRequestRepository, + _authService = authService; + + final DisableUserAccountRequestRepository + _disableUserAccountRequestRepository; + final AuthService _authService; + + /// 引数で受ける [userId] を用いて [DisableUserAccountRequest] ドキュメントを作成した後、サインアウトする。 + Future disableUserAccount({ + required String userId, + }) async { + await _createDisableUserAccountRequest(userId: userId); + await _authService.signOut(); + } + + /// 引数で受ける [userId] を用いて [DisableUserAccountRequest] ドキュメントを作成する。 + Future _createDisableUserAccountRequest({ + required String userId, + }) async { + await _disableUserAccountRequestRepository.setDisableUserAccountRequest( + userId: userId, + ); + } +} diff --git a/packages/mottai_flutter_app/lib/development/disable_user_account_request/ui/disable_user_account_request_controller.dart b/packages/mottai_flutter_app/lib/development/disable_user_account_request/ui/disable_user_account_request_controller.dart new file mode 100644 index 00000000..5ec1b03b --- /dev/null +++ b/packages/mottai_flutter_app/lib/development/disable_user_account_request/ui/disable_user_account_request_controller.dart @@ -0,0 +1,90 @@ +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../../../scaffold_messenger_controller.dart'; +import '../disable_user_account_request.dart'; + +final disableUserAccountRequestControllerProvider = + Provider.autoDispose( + (ref) => DisableUserAccountRequestController( + disableUserAccountRequestService: + ref.watch(disableUserAccountRequestServiceProvider), + appScaffoldMessengerController: + ref.watch(appScaffoldMessengerControllerProvider), + ), +); + +class DisableUserAccountRequestController { + const DisableUserAccountRequestController({ + required DisableUserAccountRequestService disableUserAccountRequestService, + required AppScaffoldMessengerController appScaffoldMessengerController, + }) : _disableUserAccountRequestService = disableUserAccountRequestService, + _appScaffoldMessengerController = appScaffoldMessengerController; + + final DisableUserAccountRequestService _disableUserAccountRequestService; + final AppScaffoldMessengerController _appScaffoldMessengerController; + + /// ダイアログにより退会確認を行い、退会する場合は[disableUserAccountRequest] ドキュメントを作成する + //TODO 必要なら [AuthService] の [logout] メソッドをコールする + Future disableUserAccountRequest({required String userId}) async { + await _appScaffoldMessengerController.showDialogByBuilder( + builder: (context) => AlertDialog( + title: const SelectableText('本当に退会しますか?'), + actions: [ + ElevatedButton( + onPressed: () async { + try { + await _disableUserAccountRequestService.disableUserAccount( + userId: userId, + ); + if (!context.mounted) { + return; + } + Navigator.pop(context); + await _showDisableUserAccountCompletedDialog(); + } on FirebaseException catch (e) { + _appScaffoldMessengerController + .showSnackBarByFirebaseException(e); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + ), + child: const Text( + '退会する', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('戻る'), + ), + ], + ), + ); + } + + /// 退会処理が完了したことをユーザーに通知するダイアログを表示する + Future _showDisableUserAccountCompletedDialog() async { + await _appScaffoldMessengerController.showDialogByBuilder( + builder: (context) => AlertDialog( + title: const Text('退会処理が完了しました。'), + content: const Text('ご利用いただきありがとうございました。'), + actions: [ + ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('閉じる'), + ), + ], + ), + ); + } +} diff --git a/packages/mottai_flutter_app/lib/development/firebase_storage/ui/firebase_storage.dart b/packages/mottai_flutter_app/lib/development/firebase_storage/ui/firebase_storage.dart index 98ac6408..e4fc76bb 100644 --- a/packages/mottai_flutter_app/lib/development/firebase_storage/ui/firebase_storage.dart +++ b/packages/mottai_flutter_app/lib/development/firebase_storage/ui/firebase_storage.dart @@ -116,7 +116,7 @@ class _FirebaseStorageSampleState else const Center( child: Text('未アップロード'), - ) + ), ], ), ); diff --git a/packages/mottai_flutter_app/lib/development/force_update/ui/force_update.dart b/packages/mottai_flutter_app/lib/development/force_update/ui/force_update.dart deleted file mode 100644 index 94bbc075..00000000 --- a/packages/mottai_flutter_app/lib/development/force_update/ui/force_update.dart +++ /dev/null @@ -1,121 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; - -import '../../../force_update/force_update.dart'; -import '../../../scaffold_messenger_controller.dart'; - -@RoutePage() -class ForceUpdateSamplePage extends ConsumerWidget { - const ForceUpdateSamplePage({super.key}); - - /// [AutoRoute] で指定するパス文字列。 - static const path = '/forceUpdateSample'; - - /// [ForceUpdateSamplePage] に遷移する際に `context.router.pushNamed` で指定する文字列。 - static const location = path; - - @override - Widget build(BuildContext context, WidgetRef ref) { - return Scaffold( - appBar: AppBar( - title: const Text('フォースアップデート情報'), - ), - body: ref.watch(forceUpdateStreamProvider).when( - data: (forceUpdateConfig) => SingleChildScrollView( - child: forceUpdateConfig != null - ? Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - title: Text( - forceUpdateConfig.androidForceUpdate.toString(), - ), - subtitle: const Text('androidForceUpdate'), - ), - ListTile( - title: Text( - forceUpdateConfig.androidLatestVersion, - ), - subtitle: const Text('androidLatestVersion'), - ), - ListTile( - title: Text( - forceUpdateConfig.androidMinRequiredVersion, - ), - subtitle: const Text('androidMinRequiredVersion'), - ), - ListTile( - title: Text( - forceUpdateConfig.iOSForceUpdate.toString(), - ), - subtitle: const Text('iOSForceUpdate'), - ), - ListTile( - title: Text( - forceUpdateConfig.iOSLatestVersion, - ), - subtitle: const Text('iOSLatestVersion'), - ), - ListTile( - title: Text( - forceUpdateConfig.iOSMinRequiredVersion, - ), - subtitle: const Text('iOSMinRequiredVersion'), - ), - ListTile( - title: Text( - ref.watch(isForceUpdateRequiredProvider).toString(), - ), - subtitle: const Text('アップデートするかどうか'), - ), - // if (ref.watch(isForceUpdateProvider)) - ElevatedButton( - onPressed: () async { - await ref - .read(appScaffoldMessengerControllerProvider) - .showDialogByBuilder( - builder: (_) => const _ForceUpdateDialog(), - barrierDismissible: false, - ); - }, - child: const Text('ダイアログ表示'), - ), - ], - ) - : const SizedBox(), - ), - error: (_, __) => const SizedBox(), - loading: () => const Center(child: CircularProgressIndicator()), - ), - ); - } -} - -class _ForceUpdateDialog extends StatelessWidget { - const _ForceUpdateDialog(); - - @override - Widget build(BuildContext context) { - return AlertDialog( - title: - const Text('最新バージョンを App Store または Google Play Store でダウンロードしてください'), - actions: [ - ElevatedButton( - onPressed: () { - Navigator.pop(context); - // TODO:アプリAppStoreへ飛ばす処理を追加 - }, - child: const Text('App Store'), - ), - ElevatedButton( - onPressed: () { - Navigator.pop(context); - // TODO:アプリGoogle Play Storeへ飛ばす処理を追加 - }, - child: const Text('Google Play Store'), - ), - ], - ); - } -} diff --git a/packages/mottai_flutter_app/lib/development/user_generate_content/ui/user_generate_content_sample.dart b/packages/mottai_flutter_app/lib/development/user_generate_content/ui/user_generate_content_sample.dart new file mode 100644 index 00000000..09c32425 --- /dev/null +++ b/packages/mottai_flutter_app/lib/development/user_generate_content/ui/user_generate_content_sample.dart @@ -0,0 +1,214 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../../../auth/ui/auth_dependent_builder.dart'; +import '../../../block/block.dart'; + +@RoutePage() +class UgcSamplePage extends ConsumerStatefulWidget { + const UgcSamplePage({super.key}); + + /// [AutoRoute] で指定するパス文字列。 + static const path = '/ugcSample'; + + /// [UgcSamplePage] に遷移する際に `context.router.pushNamed` で指定する文字列。 + static const location = path; + + @override + UgcSampleState createState() => UgcSampleState(); +} + +class UgcSampleState extends ConsumerState { + /// [AutoRoute] で指定するパス文字列。 + static const path = '/ugcSample'; + + /// [UgcSamplePage] に遷移する際に `context.router.pushNamed` で指定する文字列。 + static const location = path; + + static const _jobIds = [ + '5Cqov45ZLxR5sH3JbEev', + '9usk0ceJRA9OOcWkQGdX', + 'PYRsrMSOApEgZ6lzMuUK', + 'Riy95dPkPPFIEH1kf0ks', + 'aQf7MCsj88LoRpfYOPqX', + 'xQvXmRr26wythvz9wizf', + ]; + + static const _reviewIds = [ + '2TPCqigw8xTvju9t6hAh', + 'MlWDXBME1CSLdTNaNsmF', + ]; + + String? _selectedReportJobId; + String? _selectedReportReviewId; + String? _selectedBlockJobId; + String? _selectedBlockReviewId; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('UGC機能サンプルページ')), + body: AuthDependentBuilder( + onAuthenticated: (userId) { + return SingleChildScrollView( + child: Column( + children: [ + // =====Jobの通報機能===== + _UgcContent( + title: 'Jobの通報処理', + executeText: 'Jobを通報', + targetItems: _jobIds, + item: _selectedReportJobId, + onSelected: (jobId) => setState(() { + _selectedReportJobId = jobId; + }), + onPressed: () { + final jobId = _selectedReportJobId; + if (jobId == null) { + return; + } + ref.watch(blockJobServiceProvider).create( + userId: userId, + jobId: jobId, + ); + }, + ), + + // =====Reviewの通報機能===== + _UgcContent( + title: 'Reviewの通報処理', + executeText: 'Reviewを通報', + targetItems: _reviewIds, + item: _selectedReportReviewId, + onSelected: (reviewId) => setState(() { + _selectedReportReviewId = reviewId; + }), + onPressed: () { + final reviewId = _selectedReportReviewId; + if (reviewId == null) { + return; + } + ref.watch(blockReviewServiceProvider).create( + userId: userId, + reviewId: reviewId, + ); + }, + ), + + // =====Jobのブロック機能===== + _UgcContent( + title: 'Jobのブロック処理', + executeText: 'Jobをブロック', + targetItems: _jobIds, + item: _selectedBlockJobId, + onSelected: (jobId) => setState(() { + _selectedBlockJobId = jobId; + }), + onPressed: () { + final jobId = _selectedBlockJobId; + if (jobId == null) { + return; + } + ref.watch(blockJobServiceProvider).create( + userId: userId, + jobId: jobId, + ); + }, + ), + + // =====Reviewのブロック機能===== + _UgcContent( + title: 'Reviewのブロック処理', + executeText: 'Reviewをブロック', + targetItems: _reviewIds, + item: _selectedBlockReviewId, + onSelected: (reviewId) => setState(() { + _selectedBlockReviewId = reviewId; + }), + onPressed: () { + final reviewId = _selectedBlockReviewId; + if (reviewId == null) { + return; + } + ref.watch(blockReviewServiceProvider).create( + userId: userId, + reviewId: reviewId, + ); + }, + ), + ], + ), + ); + }, + ), + ); + } +} + +class _UgcContent extends StatelessWidget { + const _UgcContent({ + required this.targetItems, + required this.title, + required this.executeText, + this.item, + this.onPressed, + this.onSelected, + }); + + /// 対象がドロップダウンで選択されたときのコールバック + final void Function(String?)? onSelected; + + /// 選択中のアイテム + final String? item; + + /// 対象のアイテムリスト + final List targetItems; + + /// コンテンツのタイトル + final String title; + + /// UGCの処理実行ボタンが押下されたときのコールバック + final void Function()? onPressed; + + /// 実行を促すボタンのテキスト + final String executeText; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + children: [ + Text( + title, + style: Theme.of(context).textTheme.headlineSmall, + ), + Padding( + padding: const EdgeInsets.all(16), + child: DropdownButton( + value: item, + icon: const Icon(Icons.arrow_drop_down), + iconSize: 30, + isExpanded: true, + onChanged: onSelected, + items: targetItems + .map( + (item) => DropdownMenuItem( + value: item, + child: Text(item), + ), + ) + .toList(), + ), + ), + ElevatedButton.icon( + icon: const Icon(Icons.lightbulb), + label: Text(executeText), + onPressed: onPressed, + ), + ], + ), + ); + } +} diff --git a/packages/mottai_flutter_app/lib/firestore_repository.dart b/packages/mottai_flutter_app/lib/firestore_repository.dart index b32a032d..b828193a 100644 --- a/packages/mottai_flutter_app/lib/firestore_repository.dart +++ b/packages/mottai_flutter_app/lib/firestore_repository.dart @@ -1,12 +1,26 @@ import 'package:firebase_common/firebase_common.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +final blockedJobRepositoryProvider = Provider.autoDispose( + (_) => BlockedJobRepository(), +); + +final blockedReviewRepositoryProvider = + Provider.autoDispose( + (_) => BlockedReviewRepository(), +); + final chatMessageRepositoryProvider = Provider.autoDispose((_) => ChatMessageRepository()); final chatRoomRepositoryProvider = Provider.autoDispose((_) => ChatRoomRepository()); +final disableUserAccountRequestRepositoryProvider = + Provider.autoDispose( + (_) => DisableUserAccountRequestRepository(), +); + final forceUpdateConfigRepositoryProvider = Provider.autoDispose( (_) => ForceUpdateConfigRepository(), diff --git a/packages/mottai_flutter_app/lib/force_update/ui/force_update.dart b/packages/mottai_flutter_app/lib/force_update/ui/force_update.dart index 8b137891..b796207e 100644 --- a/packages/mottai_flutter_app/lib/force_update/ui/force_update.dart +++ b/packages/mottai_flutter_app/lib/force_update/ui/force_update.dart @@ -1 +1,31 @@ +import 'package:flutter/material.dart'; +class ForceUpdateDialog extends StatelessWidget { + const ForceUpdateDialog({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('アプリの更新'), + content: const Text( + '最新バージョンのアプリがご利用可能です。下記のボタンを押してダウンロードしてください。', + ), + actions: [ + if (Theme.of(context).platform == TargetPlatform.iOS) + ElevatedButton( + onPressed: () { + // TODO: AppStore へ飛ばす処理を追加する + }, + child: const Text('App Store'), + ) + else if (Theme.of(context).platform == TargetPlatform.android) + ElevatedButton( + onPressed: () { + // TODO: Google Play Store へ飛ばす処理を追加する + }, + child: const Text('Google Play Store'), + ), + ], + ); + } +} diff --git a/packages/mottai_flutter_app/lib/host/ui/host.dart b/packages/mottai_flutter_app/lib/host/ui/host.dart index 7d0299a3..ffc4b8d3 100644 --- a/packages/mottai_flutter_app/lib/host/ui/host.dart +++ b/packages/mottai_flutter_app/lib/host/ui/host.dart @@ -159,7 +159,7 @@ class HostPageBody extends ConsumerWidget { .ifIsEmpty('お手伝いの場所が登録されていません'), ) else - const Text('お手伝いの場所が登録されていません') + const Text('お手伝いの場所が登録されていません'), ], ), ), diff --git a/packages/mottai_flutter_app/lib/host/ui/host_form.dart b/packages/mottai_flutter_app/lib/host/ui/host_form.dart index 65eb48a6..6ea4dec4 100644 --- a/packages/mottai_flutter_app/lib/host/ui/host_form.dart +++ b/packages/mottai_flutter_app/lib/host/ui/host_form.dart @@ -9,13 +9,22 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../development/firebase_storage/firebase_storage.dart'; import '../../development/firebase_storage/ui/firebase_storage_controller.dart'; +import '../../exception.dart'; import '../../host_location/ui/host_location_select_dialog.dart'; +import '../../loading/ui/loading.dart'; +import '../../scaffold_messenger_controller.dart'; import '../../widgets/optional_badge.dart'; import 'host_controller.dart'; +// TODO: 現在の位置情報を反映する。 /// 東京駅の緯度経度(テスト用)初期位置は現在地? const _tokyoStation = LatLng(35.681236, 139.767125); +class _ValidationException extends AppException { + const _ValidationException({required String message}) + : super(message: message); +} + enum _HostFormMode { create, update } /// - `create` の場合、ログイン済みの `workerId`(ユーザー ID) @@ -68,9 +77,6 @@ class HostFormState extends ConsumerState { /// [Host.urls] のテキストフィールド用コントローラ。 final List _urlControllers = []; - // /// [HostController] インスタンス。 - // late final HostController _controller; - /// 選択中のホストの位置を表す [Geo]. late Geo _geo; @@ -145,32 +151,24 @@ class HostFormState extends ConsumerState { ); } - // bool _validate(File? pickedImageFile) { - // final textValidate = _formKey.currentState?.validate(); - // final isExistImage = - // (pickedImageFile != null) || (widget._host?.imageUrl != null); - // final isSelectedHostType = _selectedHostTypes.isNotEmpty; - - // var result = textValidate ?? true; // テキストの検証結果を代入(すべてのアンドをとるため) - - // // エラーメッセージの初期化 - // ref.watch(hostTypeErrorStateProvider.notifier).state = null; - // ref.watch(imageFieldErrorStateProvider.notifier).state = null; - - // // 画像のチェック - // if (!isExistImage) { - // ref.watch(imageFieldErrorStateProvider.notifier).state = '画像を選択してください'; - // result = false; - // } - - // // ホストタイプのチェック - // if (!isSelectedHostType) { - // ref.watch(hostTypeErrorStateProvider.notifier).state = - // 'ホストタイプを1つ以上選択してください'; - // result = false; - // } - // return result; - // } + /// フォームの入力前にバリデーションを行い、不適切な場合には例外をスローする。 + void _validate() { + if (ref.read(pickedImageFileStateProvider) == null) { + throw const _ValidationException(message: '画像を選択してください。'); + } + if (_nameController.text.isEmpty) { + throw const _ValidationException(message: '名前を入力してください。'); + } + if (_selectedHostTypes.isEmpty) { + throw const _ValidationException(message: 'ホストタイプを選択してください。'); + } + if (_introductionController.text.isEmpty) { + throw const _ValidationException(message: '自己紹介を入力してください。'); + } + if (_locationController.text.isEmpty) { + throw const _ValidationException(message: '場所を入力してください。'); + } + } @override Widget build(BuildContext context) { @@ -372,39 +370,49 @@ class HostFormState extends ConsumerState { child: Center( child: ElevatedButton( onPressed: () { - // if (!_validate(pickedImageFile)) { - // return; - // } - final controller = - ref.read(hostControllerProvider(widget._hostId)); - - switch (_hostFormMode) { - case _HostFormMode.create: - controller.create( - workerId: widget._hostId, - displayName: _nameController.text, - introduction: _introductionController.text, - imageFile: pickedImageFile!, - hostTypes: _selectedHostTypes.toSet(), - urls: _urlControllers - .map((controller) => controller.text) - .toList(), - address: _locationController.text, - geo: _geo, - ); - case _HostFormMode.update: - controller.update( - hostId: widget._hostId, - displayName: _nameController.text, - introduction: _introductionController.text, - imageFile: pickedImageFile, - hostTypes: _selectedHostTypes.toSet(), - urls: _urlControllers - .map((controller) => controller.text) - .toList(), - address: _locationController.text, - geo: _geo, - ); + ref + .read(overlayLoadingStateProvider.notifier) + .update((_) => true); + try { + _validate(); + final controller = + ref.read(hostControllerProvider(widget._hostId)); + switch (_hostFormMode) { + case _HostFormMode.create: + controller.create( + workerId: widget._hostId, + displayName: _nameController.text, + introduction: _introductionController.text, + imageFile: pickedImageFile!, + hostTypes: _selectedHostTypes.toSet(), + urls: _urlControllers + .map((controller) => controller.text) + .toList(), + address: _locationController.text, + geo: _geo, + ); + case _HostFormMode.update: + controller.update( + hostId: widget._hostId, + displayName: _nameController.text, + introduction: _introductionController.text, + imageFile: pickedImageFile, + hostTypes: _selectedHostTypes.toSet(), + urls: _urlControllers + .map((controller) => controller.text) + .toList(), + address: _locationController.text, + geo: _geo, + ); + } + } on AppException catch (e) { + ref + .read(appScaffoldMessengerControllerProvider) + .showSnackBarByException(e); + } finally { + ref + .read(overlayLoadingStateProvider.notifier) + .update((_) => false); } }, child: const Text('この内容で登録する'), diff --git a/packages/mottai_flutter_app/lib/map/ui/map.dart b/packages/mottai_flutter_app/lib/map/ui/map.dart index 02bb60a4..14eb150e 100644 --- a/packages/mottai_flutter_app/lib/map/ui/map.dart +++ b/packages/mottai_flutter_app/lib/map/ui/map.dart @@ -257,7 +257,7 @@ class MapPageState extends ConsumerState { ); }, icon: const Icon(Icons.near_me), - ) + ), ], ), ), diff --git a/packages/mottai_flutter_app/lib/pagination/firestore_pagination.dart b/packages/mottai_flutter_app/lib/pagination/firestore_pagination.dart index 67e1f46c..890cd8a1 100644 --- a/packages/mottai_flutter_app/lib/pagination/firestore_pagination.dart +++ b/packages/mottai_flutter_app/lib/pagination/firestore_pagination.dart @@ -1,13 +1,15 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +// TODO: AsyncNotifier で書き換える /// Firestore のパジネーションを行うための [StateNotifier]. class FirestorePaginationStateNotifier extends StateNotifier>> { FirestorePaginationStateNotifier({ required this.fetch, required this.idFromObject, + int initialPerPage = 10, }) : super(AsyncValue>.data(const [])) { - _initialize(); + _initialize(perPage: initialPerPage); } /// データを取得する関数。 @@ -25,9 +27,6 @@ class FirestorePaginationStateNotifier /// 次のページがあるかどうか。 bool _hasNext = true; - /// リクエスト一回あたりの取得件数のデフォルト値。 - static const _defaultPerPage = 10; - /// 取得処理中かどうか。 bool get isFetching => _isFetching; @@ -35,13 +34,16 @@ class FirestorePaginationStateNotifier bool get hasNext => _hasNext; /// 初期化処理。 - Future> _initialize({int perPage = _defaultPerPage}) async { + Future> _initialize({required int perPage}) async { state = AsyncValue>.loading(); _isFetching = true; try { final items = await fetch(perPage, null); + state = AsyncValue.data(items); _hasNext = items.isNotEmpty; - state = AsyncValue>.data(items); + if (items.isNotEmpty) { + _lastFetchedId = idFromObject(items.last); + } } on Exception catch (e, s) { state = AsyncValue>.error(e, s); } finally { @@ -51,19 +53,20 @@ class FirestorePaginationStateNotifier } /// 次のページを取得する。 - Future fetchNext({int perPage = _defaultPerPage}) async { + Future fetchNext({required int perPage}) async { final items = state; if (_isFetching || items.isRefreshing || !items.hasValue || !_hasNext) { return; } _isFetching = true; try { - final result = await fetch(perPage, _lastFetchedId); - final newItems = [...state.valueOrNull ?? [], ...result]; + final items = await fetch(perPage, _lastFetchedId); + final newItems = [...state.valueOrNull ?? [], ...items]; state = AsyncValue.data(newItems); - // _hasNext = result.isNotEmpty; - _hasNext = result.length == perPage; - _lastFetchedId = idFromObject(result.last); + _hasNext = items.isNotEmpty; + if (items.isNotEmpty) { + _lastFetchedId = idFromObject(items.last); + } } on Exception catch (e, stackTrace) { state = AsyncValue>.error(e, stackTrace); return; @@ -71,19 +74,4 @@ class FirestorePaginationStateNotifier _isFetching = false; } } - - /// リフレッシュする。 - Future refresh({int perPage = _defaultPerPage}) async { - _isFetching = true; - _lastFetchedId = null; - try { - final items = await fetch(perPage, null); - _hasNext = items.isNotEmpty; - state = AsyncValue>.data(items); - } on Exception catch (e, s) { - state = AsyncValue>.error(e, s); - } finally { - _isFetching = false; - } - } } diff --git a/packages/mottai_flutter_app/lib/pagination/ui/pagination_list_view.dart b/packages/mottai_flutter_app/lib/pagination/ui/pagination_list_view.dart index 2f3751a2..a73cdd9f 100644 --- a/packages/mottai_flutter_app/lib/pagination/ui/pagination_list_view.dart +++ b/packages/mottai_flutter_app/lib/pagination/ui/pagination_list_view.dart @@ -6,11 +6,14 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../firestore_pagination.dart'; -/// [FirestorePaginationStateNotifier] を使用した無限スクロールの [ListView] UI. -class FirestorePaginationListView extends ConsumerWidget { - const FirestorePaginationListView({ +/// [FirestorePaginationStateNotifier] を使用したパジネーション UI. +class FirestorePaginationView extends ConsumerWidget { + const FirestorePaginationView({ required this.stateNotifierProvider, - required this.itemBuilder, + required this.whenData, + required this.whenEmpty, + this.perPage = 10, + this.scrollValueThreshold = 0.8, this.showDebugIndicator = false, super.key, }); @@ -20,13 +23,19 @@ class FirestorePaginationListView extends ConsumerWidget { AsyncValue>> stateNotifierProvider; /// [ListView.builder] で表示する各要素のビルダー関数。 - final Widget Function(BuildContext, T) itemBuilder; + final Widget Function(BuildContext, List) whenData; - /// 開発時のみ表示する、無限スクロールのデバッグ用ウィジェットを表示するか。 - final bool showDebugIndicator; + /// 表示するものがないときに表示する内容 + final Widget Function(BuildContext) whenEmpty; + + /// 一度に取得する件数。 + final int perPage; /// 画面の何割をスクロールした時点で次の _limit 件のメッセージを取得するか。 - static const _scrollValueThreshold = 0.8; + final double scrollValueThreshold; + + /// デバッグ用のウィジェットを表示するかどうか。 + final bool showDebugIndicator; @override Widget build(BuildContext context, WidgetRef ref) { @@ -40,18 +49,12 @@ class FirestorePaginationListView extends ConsumerWidget { onNotification: (notification) { final metrics = notification.metrics; final scrollValue = metrics.pixels / metrics.maxScrollExtent; - if (scrollValue > _scrollValueThreshold) { - notifier.fetchNext(); + if (scrollValue > scrollValueThreshold) { + notifier.fetchNext(perPage: perPage); } return false; }, - child: ListView.builder( - itemCount: items.length, - itemBuilder: (context, index) { - final item = items[index]; - return itemBuilder(context, item); - }, - ), + child: whenData(context, items), ), if (kDebugMode && showDebugIndicator) Positioned( @@ -71,7 +74,6 @@ class FirestorePaginationListView extends ConsumerWidget { } } -// TODO: あとで消す。 /// 開発時のみ表示する、無限スクロールのデバッグ用ウィジェット。 class _DebugIndicator extends ConsumerWidget { const _DebugIndicator({ @@ -79,7 +81,7 @@ class _DebugIndicator extends ConsumerWidget { required this.items, }); - /// [FirestorePaginationStateNotifier] + /// [FirestorePaginationStateNotifier] インスタンス。 final FirestorePaginationStateNotifier notifier; /// 取得したアイテム一覧。 diff --git a/packages/mottai_flutter_app/lib/review/ui/reviews.dart b/packages/mottai_flutter_app/lib/review/ui/reviews.dart index 04aadbab..e0f60cff 100644 --- a/packages/mottai_flutter_app/lib/review/ui/reviews.dart +++ b/packages/mottai_flutter_app/lib/review/ui/reviews.dart @@ -21,25 +21,32 @@ class ReviewsPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return FirestorePaginationListView( + return FirestorePaginationView( stateNotifierProvider: reviewsStateNotifierProvider, - itemBuilder: (context, review) => MaterialVerticalCard( - headerImageUrl: ref.watch(workerImageUrlProvider(review.workerId)), - header: ref.watch(workerDisplayNameProvider(review.workerId)), - subhead: review.createdAt?.formatRelativeDate(), - imageUrl: review.imageUrl, - title: review.title, - content: review.content, - menuButtonOnPressed: () {}, - actions: [ - ElevatedButton( - onPressed: () { - // TODO: 感想詳細ページへ遷移する - }, - child: const Text('もっと見る'), - ), - ], + whenData: (context, reviews) => ListView.builder( + itemCount: reviews.length, + itemBuilder: (context, index) { + final review = reviews[index]; + return MaterialVerticalCard( + headerImageUrl: ref.watch(workerImageUrlProvider(review.workerId)), + header: ref.watch(workerDisplayNameProvider(review.workerId)), + subhead: review.createdAt?.formatRelativeDate(), + imageUrl: review.imageUrl, + title: review.title, + content: review.content, + menuButtonOnPressed: () {}, + actions: [ + ElevatedButton( + onPressed: () { + // TODO: 感想詳細ページへ遷移する + }, + child: const Text('もっと見る'), + ), + ], + ); + }, ), + whenEmpty: (_) => const SizedBox(), ); } } diff --git a/packages/mottai_flutter_app/lib/root/ui/root.dart b/packages/mottai_flutter_app/lib/root/ui/root.dart index c9d56827..904edef7 100644 --- a/packages/mottai_flutter_app/lib/root/ui/root.dart +++ b/packages/mottai_flutter_app/lib/root/ui/root.dart @@ -6,9 +6,13 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../assets.dart'; import '../../auth/auth.dart'; import '../../auth/ui/auth_controller.dart'; +import '../../auth/ui/auth_dependent_builder.dart'; import '../../development/development_items/ui/development_items.dart'; +import '../../development/disable_user_account_request/ui/disable_user_account_request_controller.dart'; import '../../development/email_and_password_sign_in/ui/email_and_password_sign_in.dart'; import '../../development/sign_in/ui/sign_in.dart'; +import '../../force_update/force_update.dart'; +import '../../force_update/ui/force_update.dart'; import '../../package_info.dart'; import '../../push_notification/firebase_messaging.dart'; import '../../router/router.gr.dart'; @@ -59,43 +63,55 @@ class _RootPageState extends ConsumerState { @override Widget build(BuildContext context) { - return AutoTabsScaffold( - key: ref.watch(rootPageKey), - appBarBuilder: (_, __) => AppBar( - title: Image.asset(MottaiAssets.appBarLogo, height: 40), - ), - drawer: const Drawer(child: _DrawerChild()), - routes: const [ - MapRoute(), - ChatRoomsRoute(), - ReviewsRoute(), - MyAccountRoute(), - ], - bottomNavigationBuilder: (_, tabsRouter) { - return BottomNavigationBar( - type: BottomNavigationBarType.fixed, - currentIndex: tabsRouter.activeIndex, - onTap: tabsRouter.setActiveIndex, - items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.search), - label: '探す', - ), - BottomNavigationBarItem( - icon: Icon(Icons.chat), - label: 'チャット', - ), - BottomNavigationBarItem( - icon: Icon(Icons.record_voice_over), - label: '感想', - ), - BottomNavigationBarItem( - icon: Icon(Icons.person), - label: 'アカウント', - ), + return Stack( + fit: StackFit.expand, + children: [ + AutoTabsScaffold( + key: ref.watch(rootPageKey), + appBarBuilder: (_, __) => AppBar( + title: Image.asset(MottaiAssets.appBarLogo, height: 40), + ), + drawer: const Drawer(child: _DrawerChild()), + routes: const [ + MapRoute(), + ChatRoomsRoute(), + ReviewsRoute(), + MyAccountRoute(), ], - ); - }, + bottomNavigationBuilder: (_, tabsRouter) { + return BottomNavigationBar( + type: BottomNavigationBarType.fixed, + currentIndex: tabsRouter.activeIndex, + onTap: tabsRouter.setActiveIndex, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.search), + label: '探す', + ), + BottomNavigationBarItem( + icon: Icon(Icons.chat), + label: 'チャット', + ), + BottomNavigationBarItem( + icon: Icon(Icons.record_voice_over), + label: '感想', + ), + BottomNavigationBarItem( + icon: Icon(Icons.person), + label: 'アカウント', + ), + ], + ); + }, + ), + // 強制アップデートが必要な場合に、ストアへの導線を持つダイアログを表示する + // そのダイアログ以外の操作ができないよう画面全体をColoredBoxで覆い、その上にダイアログのみを表示する + if (ref.watch(isForceUpdateRequiredProvider)) + const ColoredBox( + color: Colors.black54, + child: ForceUpdateDialog(), + ), + ], ); } } @@ -168,7 +184,7 @@ class _DrawerChild extends ConsumerWidget { leading: const Icon(Icons.login), title: const Text('サインイン(ソーシャル)'), onTap: () => context.router.pushNamed(SignInSamplePage.location), - ) + ), ], ListTile( leading: const Icon(Icons.notifications), @@ -194,6 +210,20 @@ class _DrawerChild extends ConsumerWidget { title: const Text('開発ページへ'), onTap: () => context.router.pushNamed(DevelopmentItemsPage.location), ), + AuthDependentBuilder( + onAuthenticated: (userId) => ListTile( + leading: const Icon(Icons.person_off), + title: const Text('退会する'), + onTap: () async { + await ref + .read(disableUserAccountRequestControllerProvider) + .disableUserAccountRequest( + userId: userId, + ); + }, + ), + onUnAuthenticated: () => const SizedBox(), + ), ], ); } diff --git a/packages/mottai_flutter_app/lib/router/router.dart b/packages/mottai_flutter_app/lib/router/router.dart index 95b09124..87bbb52e 100644 --- a/packages/mottai_flutter_app/lib/router/router.dart +++ b/packages/mottai_flutter_app/lib/router/router.dart @@ -7,7 +7,6 @@ import '../development/development_items/ui/development_items.dart'; import '../development/email_and_password_sign_in/ui/email_and_password_sign_in.dart'; import '../development/firebase_messaging/ui/firebase_messaging.dart'; import '../development/firebase_storage/ui/firebase_storage.dart'; -import '../development/force_update/ui/force_update.dart'; import '../development/generic_image/ui/generic_images.dart'; import '../development/image_detail_view/ui/image_detail_view_stub.dart'; import '../development/image_picker/ui/image_picker_sample.dart'; @@ -15,6 +14,7 @@ import '../development/in_review/ui/in_review.dart'; import '../development/sample_todo/ui/todos.dart'; import '../development/sign_in/ui/sign_in.dart'; import '../development/user_fcm_token/ui/user_fcm_token.dart'; +import '../development/user_generate_content/ui/user_generate_content_sample.dart'; import '../development/user_social_login/user_social_login.dart'; import '../development/web_link/ui/web_link_stub.dart'; import '../host/ui/host.dart'; @@ -89,10 +89,6 @@ class AppRouter extends $AppRouter { path: HostUpdatePage.path, page: HostUpdateRoute.page, ), - // AutoRoute( - // path: UserPage.path, - // page: UserRoute.page, - // ), AutoRoute( path: CreateOrUpdateWorkerPage.path, page: CreateOrUpdateWorkerRoute.page, @@ -115,10 +111,6 @@ class AppRouter extends $AppRouter { path: FirebaseStorageSamplePage.path, page: FirebaseStorageSampleRoute.page, ), - AutoRoute( - path: ForceUpdateSamplePage.path, - page: ForceUpdateSampleRoute.page, - ), AutoRoute( path: GenericImagesPage.path, page: GenericImagesRoute.page, @@ -155,6 +147,10 @@ class AppRouter extends $AppRouter { path: UserFcmTokenPage.path, page: UserFcmTokenRoute.page, ), + AutoRoute( + path: UgcSamplePage.path, + page: UgcSampleRoute.page, + ), AutoRoute( path: FirebaseMessagingPage.path, page: FirebaseMessagingRoute.page, diff --git a/packages/mottai_flutter_app/lib/router/router.gr.dart b/packages/mottai_flutter_app/lib/router/router.gr.dart index cbd18e29..398eddad 100644 --- a/packages/mottai_flutter_app/lib/router/router.gr.dart +++ b/packages/mottai_flutter_app/lib/router/router.gr.dart @@ -21,35 +21,35 @@ import 'package:mottai_flutter_app/development/firebase_messaging/ui/firebase_me as _i7; import 'package:mottai_flutter_app/development/firebase_storage/ui/firebase_storage.dart' as _i8; -import 'package:mottai_flutter_app/development/force_update/ui/force_update.dart' - as _i9; import 'package:mottai_flutter_app/development/generic_image/ui/generic_images.dart' - as _i10; + as _i9; import 'package:mottai_flutter_app/development/image_detail_view/ui/image_detail_view_stub.dart' - as _i14; + as _i13; import 'package:mottai_flutter_app/development/image_picker/ui/image_picker_sample.dart' - as _i15; + as _i14; import 'package:mottai_flutter_app/development/in_review/ui/in_review.dart' - as _i16; + as _i15; import 'package:mottai_flutter_app/development/sample_todo/ui/todos.dart' - as _i25; -import 'package:mottai_flutter_app/development/sign_in/ui/sign_in.dart' as _i24; + as _i24; +import 'package:mottai_flutter_app/development/sign_in/ui/sign_in.dart' as _i23; import 'package:mottai_flutter_app/development/user_fcm_token/ui/user_fcm_token.dart' as _i26; +import 'package:mottai_flutter_app/development/user_generate_content/ui/user_generate_content_sample.dart' + as _i25; import 'package:mottai_flutter_app/development/user_social_login/user_social_login.dart' as _i27; import 'package:mottai_flutter_app/development/web_link/ui/web_link_stub.dart' as _i28; -import 'package:mottai_flutter_app/host/ui/host.dart' as _i12; -import 'package:mottai_flutter_app/host/ui/host_create.dart' as _i11; -import 'package:mottai_flutter_app/host/ui/host_update.dart' as _i13; -import 'package:mottai_flutter_app/job/ui/job_create.dart' as _i17; -import 'package:mottai_flutter_app/job/ui/job_detail.dart' as _i18; -import 'package:mottai_flutter_app/job/ui/job_update.dart' as _i19; -import 'package:mottai_flutter_app/map/ui/map.dart' as _i20; -import 'package:mottai_flutter_app/my_account/ui/my_account.dart' as _i21; -import 'package:mottai_flutter_app/review/ui/reviews.dart' as _i22; -import 'package:mottai_flutter_app/root/ui/root.dart' as _i23; +import 'package:mottai_flutter_app/host/ui/host.dart' as _i11; +import 'package:mottai_flutter_app/host/ui/host_create.dart' as _i10; +import 'package:mottai_flutter_app/host/ui/host_update.dart' as _i12; +import 'package:mottai_flutter_app/job/ui/job_create.dart' as _i16; +import 'package:mottai_flutter_app/job/ui/job_detail.dart' as _i17; +import 'package:mottai_flutter_app/job/ui/job_update.dart' as _i18; +import 'package:mottai_flutter_app/map/ui/map.dart' as _i19; +import 'package:mottai_flutter_app/my_account/ui/my_account.dart' as _i20; +import 'package:mottai_flutter_app/review/ui/reviews.dart' as _i21; +import 'package:mottai_flutter_app/root/ui/root.dart' as _i22; import 'package:mottai_flutter_app/worker/ui/create_or_update_worker.dart' as _i4; import 'package:mottai_flutter_app/worker/ui/worker.dart' as _i29; @@ -121,22 +121,16 @@ abstract class $AppRouter extends _i30.RootStackRouter { child: const _i8.FirebaseStorageSamplePage(), ); }, - ForceUpdateSampleRoute.name: (routeData) { - return _i30.AutoRoutePage( - routeData: routeData, - child: const _i9.ForceUpdateSamplePage(), - ); - }, GenericImagesRoute.name: (routeData) { return _i30.AutoRoutePage( routeData: routeData, - child: const _i10.GenericImagesPage(), + child: const _i9.GenericImagesPage(), ); }, HostCreateRoute.name: (routeData) { return _i30.AutoRoutePage( routeData: routeData, - child: const _i11.HostCreatePage(), + child: const _i10.HostCreatePage(), ); }, HostRoute.name: (routeData) { @@ -145,7 +139,7 @@ abstract class $AppRouter extends _i30.RootStackRouter { orElse: () => HostRouteArgs(userId: pathParams.getString('userId'))); return _i30.AutoRoutePage( routeData: routeData, - child: _i12.HostPage( + child: _i11.HostPage( userId: args.userId, key: args.key, ), @@ -158,7 +152,7 @@ abstract class $AppRouter extends _i30.RootStackRouter { HostUpdateRouteArgs(hostId: pathParams.getString('hostId'))); return _i30.AutoRoutePage( routeData: routeData, - child: _i13.HostUpdatePage( + child: _i12.HostUpdatePage( hostId: args.hostId, key: args.key, ), @@ -167,25 +161,25 @@ abstract class $AppRouter extends _i30.RootStackRouter { ImageDetailViewStubRoute.name: (routeData) { return _i30.AutoRoutePage( routeData: routeData, - child: const _i14.ImageDetailViewStubPage(), + child: const _i13.ImageDetailViewStubPage(), ); }, ImagePickerSampleRoute.name: (routeData) { return _i30.AutoRoutePage( routeData: routeData, - child: const _i15.ImagePickerSamplePage(), + child: const _i14.ImagePickerSamplePage(), ); }, InReviewRoute.name: (routeData) { return _i30.AutoRoutePage( routeData: routeData, - child: const _i16.InReviewPage(), + child: const _i15.InReviewPage(), ); }, JobCreateRoute.name: (routeData) { return _i30.AutoRoutePage( routeData: routeData, - child: const _i17.JobCreatePage(), + child: const _i16.JobCreatePage(), ); }, JobDetailRoute.name: (routeData) { @@ -195,7 +189,7 @@ abstract class $AppRouter extends _i30.RootStackRouter { JobDetailRouteArgs(jobId: pathParams.getString('jobId'))); return _i30.AutoRoutePage( routeData: routeData, - child: _i18.JobDetailPage( + child: _i17.JobDetailPage( jobId: args.jobId, key: args.key, ), @@ -208,7 +202,7 @@ abstract class $AppRouter extends _i30.RootStackRouter { JobUpdateRouteArgs(jobId: pathParams.getString('jobId'))); return _i30.AutoRoutePage( routeData: routeData, - child: _i19.JobUpdatePage( + child: _i18.JobUpdatePage( jobId: args.jobId, key: args.key, ), @@ -217,37 +211,43 @@ abstract class $AppRouter extends _i30.RootStackRouter { MapRoute.name: (routeData) { return _i30.AutoRoutePage( routeData: routeData, - child: const _i20.MapPage(), + child: const _i19.MapPage(), ); }, MyAccountRoute.name: (routeData) { return _i30.AutoRoutePage( routeData: routeData, - child: const _i21.MyAccountPage(), + child: const _i20.MyAccountPage(), ); }, ReviewsRoute.name: (routeData) { return _i30.AutoRoutePage( routeData: routeData, - child: const _i22.ReviewsPage(), + child: const _i21.ReviewsPage(), ); }, RootRoute.name: (routeData) { return _i30.AutoRoutePage( routeData: routeData, - child: const _i23.RootPage(), + child: const _i22.RootPage(), ); }, SignInSampleRoute.name: (routeData) { return _i30.AutoRoutePage( routeData: routeData, - child: const _i24.SignInSamplePage(), + child: const _i23.SignInSamplePage(), ); }, TodosRoute.name: (routeData) { return _i30.AutoRoutePage( routeData: routeData, - child: const _i25.TodosPage(), + child: const _i24.TodosPage(), + ); + }, + UgcSampleRoute.name: (routeData) { + return _i30.AutoRoutePage( + routeData: routeData, + child: const _i25.UgcSamplePage(), ); }, UserFcmTokenRoute.name: (routeData) { @@ -448,21 +448,7 @@ class FirebaseStorageSampleRoute extends _i30.PageRouteInfo { } /// generated route for -/// [_i9.ForceUpdateSamplePage] -class ForceUpdateSampleRoute extends _i30.PageRouteInfo { - const ForceUpdateSampleRoute({List<_i30.PageRouteInfo>? children}) - : super( - ForceUpdateSampleRoute.name, - initialChildren: children, - ); - - static const String name = 'ForceUpdateSampleRoute'; - - static const _i30.PageInfo page = _i30.PageInfo(name); -} - -/// generated route for -/// [_i10.GenericImagesPage] +/// [_i9.GenericImagesPage] class GenericImagesRoute extends _i30.PageRouteInfo { const GenericImagesRoute({List<_i30.PageRouteInfo>? children}) : super( @@ -476,7 +462,7 @@ class GenericImagesRoute extends _i30.PageRouteInfo { } /// generated route for -/// [_i11.HostCreatePage] +/// [_i10.HostCreatePage] class HostCreateRoute extends _i30.PageRouteInfo { const HostCreateRoute({List<_i30.PageRouteInfo>? children}) : super( @@ -490,7 +476,7 @@ class HostCreateRoute extends _i30.PageRouteInfo { } /// generated route for -/// [_i12.HostPage] +/// [_i11.HostPage] class HostRoute extends _i30.PageRouteInfo { HostRoute({ required String userId, @@ -529,7 +515,7 @@ class HostRouteArgs { } /// generated route for -/// [_i13.HostUpdatePage] +/// [_i12.HostUpdatePage] class HostUpdateRoute extends _i30.PageRouteInfo { HostUpdateRoute({ required String hostId, @@ -568,7 +554,7 @@ class HostUpdateRouteArgs { } /// generated route for -/// [_i14.ImageDetailViewStubPage] +/// [_i13.ImageDetailViewStubPage] class ImageDetailViewStubRoute extends _i30.PageRouteInfo { const ImageDetailViewStubRoute({List<_i30.PageRouteInfo>? children}) : super( @@ -582,7 +568,7 @@ class ImageDetailViewStubRoute extends _i30.PageRouteInfo { } /// generated route for -/// [_i15.ImagePickerSamplePage] +/// [_i14.ImagePickerSamplePage] class ImagePickerSampleRoute extends _i30.PageRouteInfo { const ImagePickerSampleRoute({List<_i30.PageRouteInfo>? children}) : super( @@ -596,7 +582,7 @@ class ImagePickerSampleRoute extends _i30.PageRouteInfo { } /// generated route for -/// [_i16.InReviewPage] +/// [_i15.InReviewPage] class InReviewRoute extends _i30.PageRouteInfo { const InReviewRoute({List<_i30.PageRouteInfo>? children}) : super( @@ -610,7 +596,7 @@ class InReviewRoute extends _i30.PageRouteInfo { } /// generated route for -/// [_i17.JobCreatePage] +/// [_i16.JobCreatePage] class JobCreateRoute extends _i30.PageRouteInfo { const JobCreateRoute({List<_i30.PageRouteInfo>? children}) : super( @@ -624,7 +610,7 @@ class JobCreateRoute extends _i30.PageRouteInfo { } /// generated route for -/// [_i18.JobDetailPage] +/// [_i17.JobDetailPage] class JobDetailRoute extends _i30.PageRouteInfo { JobDetailRoute({ required String jobId, @@ -663,7 +649,7 @@ class JobDetailRouteArgs { } /// generated route for -/// [_i19.JobUpdatePage] +/// [_i18.JobUpdatePage] class JobUpdateRoute extends _i30.PageRouteInfo { JobUpdateRoute({ required String jobId, @@ -702,7 +688,7 @@ class JobUpdateRouteArgs { } /// generated route for -/// [_i20.MapPage] +/// [_i19.MapPage] class MapRoute extends _i30.PageRouteInfo { const MapRoute({List<_i30.PageRouteInfo>? children}) : super( @@ -716,7 +702,7 @@ class MapRoute extends _i30.PageRouteInfo { } /// generated route for -/// [_i21.MyAccountPage] +/// [_i20.MyAccountPage] class MyAccountRoute extends _i30.PageRouteInfo { const MyAccountRoute({List<_i30.PageRouteInfo>? children}) : super( @@ -730,7 +716,7 @@ class MyAccountRoute extends _i30.PageRouteInfo { } /// generated route for -/// [_i22.ReviewsPage] +/// [_i21.ReviewsPage] class ReviewsRoute extends _i30.PageRouteInfo { const ReviewsRoute({List<_i30.PageRouteInfo>? children}) : super( @@ -744,7 +730,7 @@ class ReviewsRoute extends _i30.PageRouteInfo { } /// generated route for -/// [_i23.RootPage] +/// [_i22.RootPage] class RootRoute extends _i30.PageRouteInfo { const RootRoute({List<_i30.PageRouteInfo>? children}) : super( @@ -758,7 +744,7 @@ class RootRoute extends _i30.PageRouteInfo { } /// generated route for -/// [_i24.SignInSamplePage] +/// [_i23.SignInSamplePage] class SignInSampleRoute extends _i30.PageRouteInfo { const SignInSampleRoute({List<_i30.PageRouteInfo>? children}) : super( @@ -772,7 +758,7 @@ class SignInSampleRoute extends _i30.PageRouteInfo { } /// generated route for -/// [_i25.TodosPage] +/// [_i24.TodosPage] class TodosRoute extends _i30.PageRouteInfo { const TodosRoute({List<_i30.PageRouteInfo>? children}) : super( @@ -785,6 +771,20 @@ class TodosRoute extends _i30.PageRouteInfo { static const _i30.PageInfo page = _i30.PageInfo(name); } +/// generated route for +/// [_i25.UgcSamplePage] +class UgcSampleRoute extends _i30.PageRouteInfo { + const UgcSampleRoute({List<_i30.PageRouteInfo>? children}) + : super( + UgcSampleRoute.name, + initialChildren: children, + ); + + static const String name = 'UgcSampleRoute'; + + static const _i30.PageInfo page = _i30.PageInfo(name); +} + /// generated route for /// [_i26.UserFcmTokenPage] class UserFcmTokenRoute extends _i30.PageRouteInfo { diff --git a/packages/mottai_flutter_app/lib/user/host.dart b/packages/mottai_flutter_app/lib/user/host.dart index 0b325121..92f50af7 100644 --- a/packages/mottai_flutter_app/lib/user/host.dart +++ b/packages/mottai_flutter_app/lib/user/host.dart @@ -106,7 +106,8 @@ class HostService { // TODO: 修正する // final locations = - // await _hostLocationService.fetchHostLocationsFromHost(hostId: hostId); + // await _hostLocationService + // .fetchHostLocationsFromHost(hostId: hostId); // if (locations != null && locations.isNotEmpty) { // await _hostLocationService.update( // hostLocationId: locations.first.hostLocationId, diff --git a/packages/mottai_flutter_app/lib/user/ui/user_mode.dart b/packages/mottai_flutter_app/lib/user/ui/user_mode.dart index 98afe1e1..8ff30768 100644 --- a/packages/mottai_flutter_app/lib/user/ui/user_mode.dart +++ b/packages/mottai_flutter_app/lib/user/ui/user_mode.dart @@ -52,7 +52,7 @@ class UserModeSection extends ConsumerWidget { .read(appScaffoldMessengerControllerProvider) .showSnackBar('${newSelection.first.label}に切り替えました。'); }, - ) + ), ], ); } diff --git a/packages/mottai_flutter_app/pubspec.lock b/packages/mottai_flutter_app/pubspec.lock index 4c9826db..4c6f53db 100644 --- a/packages/mottai_flutter_app/pubspec.lock +++ b/packages/mottai_flutter_app/pubspec.lock @@ -269,10 +269,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -1090,18 +1090,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -1431,10 +1431,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" sqflite: dependency: transitive description: @@ -1511,10 +1511,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" timezone: dependency: transitive description: @@ -1635,6 +1635,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -1684,5 +1692,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0" diff --git a/packages/mottai_flutter_app/test/todo/ui/todos_test.dart b/packages/mottai_flutter_app/test/todo/ui/todos_test.dart index 9210e600..32b93e43 100644 --- a/packages/mottai_flutter_app/test/todo/ui/todos_test.dart +++ b/packages/mottai_flutter_app/test/todo/ui/todos_test.dart @@ -59,7 +59,7 @@ void main() { await tester.pumpWidget( ProviderScope( overrides: [ - todoRepositoryProvider.overrideWithValue(mockTodoRepository) + todoRepositoryProvider.overrideWithValue(mockTodoRepository), ], child: const _TodosPage(), ),