gemini nextcloud
This commit is contained in:
parent
63d944a791
commit
ac54bee9f1
9 changed files with 289 additions and 48 deletions
|
|
@ -1,4 +1,6 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
|
||||
<application
|
||||
android:label="gemi_invoice"
|
||||
android:name="${applicationName}"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
// version: 1.0.2 (Japanese Support & SHA-256 Edition)
|
||||
// version: 1.2.0 (Share-ready, Bottom-8 Hash, Template Edition)
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
|
||||
// パッケージ
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:share_plus/share_plus.dart'; // 共有機能
|
||||
import 'package:pdf/widgets.dart' as pw;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
|
@ -17,15 +22,40 @@ class InvoiceApp extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _InvoiceAppState extends State<InvoiceApp> {
|
||||
final _clientController = TextEditingController(text: "現場太郎 様");
|
||||
final _amountController = TextEditingController(text: "1500");
|
||||
String _status = "フォントを配置してPDFを発行してください";
|
||||
final _clientController = TextEditingController(text: "佐々木製作所");
|
||||
final _amountController = TextEditingController(text: "250000");
|
||||
String _status = "内容を入力してPDFを発行してください";
|
||||
|
||||
// 【設定】ファイル名のフォーマット定数
|
||||
static const String filenameTemplate = "{date}(請求){name}_{amount}円_{hash}.pdf";
|
||||
|
||||
// --- 電話帳から選ぶ ---
|
||||
Future<void> _pickContact() async {
|
||||
try {
|
||||
var status = await Permission.contacts.request();
|
||||
if (status.isGranted) {
|
||||
final contact = await FlutterContacts.openExternalPick();
|
||||
if (contact != null) {
|
||||
final fullContact = await FlutterContacts.getContact(contact.id);
|
||||
setState(() {
|
||||
String name = fullContact?.displayName ?? "名称不明";
|
||||
_clientController.text = name;
|
||||
_status = "連絡先から「$name」を読み込みました";
|
||||
});
|
||||
}
|
||||
} else if (status.isPermanentlyDenied) {
|
||||
openAppSettings();
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() => _status = "エラー: $e");
|
||||
}
|
||||
}
|
||||
|
||||
// --- PDF発行 & 共有ロジック ---
|
||||
Future<void> _generateInvoice() async {
|
||||
final pdf = pw.Document();
|
||||
|
||||
// 1. 日本語フォントの読み込み (assets/fonts/ に配置済み前提)
|
||||
// もしまだ配置してなければ、この3行をコメントアウトしてください
|
||||
// フォント読み込み
|
||||
final fontData = await rootBundle.load("assets/fonts/ipaexg.ttf");
|
||||
final ttf = pw.Font.ttf(fontData);
|
||||
final myTheme = pw.ThemeData.withFont(base: ttf, bold: ttf);
|
||||
|
|
@ -34,29 +64,30 @@ class _InvoiceAppState extends State<InvoiceApp> {
|
|||
final int unitPrice = int.tryParse(_amountController.text) ?? 0;
|
||||
final int tax = (unitPrice * 0.1).floor();
|
||||
final int total = unitPrice + tax;
|
||||
final dateStr = DateFormat('yyyy/MM/dd HH:mm').format(DateTime.now());
|
||||
|
||||
// PDFドキュメントの作成
|
||||
final now = DateTime.now();
|
||||
final dateStr = DateFormat('yyyy/MM/dd HH:mm').format(now);
|
||||
final dateFileStr = DateFormat('yyyyMMdd').format(now);
|
||||
final amountFormatter = NumberFormat("#,###");
|
||||
|
||||
// PDFレイアウト作成
|
||||
pdf.addPage(
|
||||
pw.Page(
|
||||
theme: myTheme, // 日本語テーマ適用
|
||||
theme: myTheme,
|
||||
build: (context) => pw.Column(
|
||||
crossAxisAlignment:
|
||||
pw.CrossAxisAlignment.start, // 'cross' を 'crossAxisAlignment' に修正
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text("請求書 (Invoice)", style: pw.TextStyle(fontSize: 30)),
|
||||
pw.Divider(),
|
||||
pw.Text("宛名: $clientName"),
|
||||
pw.Text("宛名: $clientName 様"),
|
||||
pw.Text("日付: $dateStr"),
|
||||
pw.SizedBox(height: 20),
|
||||
pw.Text("単価(税抜): $unitPrice 円"),
|
||||
pw.Text("消費税 (10%): $tax 円"),
|
||||
pw.Text(
|
||||
"合計金額(税込): $total 円",
|
||||
style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 20),
|
||||
),
|
||||
pw.Text("単価(税抜): ${amountFormatter.format(unitPrice)} 円"),
|
||||
pw.Text("消費税 (10%): ${amountFormatter.format(tax)} 円"),
|
||||
pw.Text("合計金額(税込): ${amountFormatter.format(total)} 円",
|
||||
style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 20)),
|
||||
pw.SizedBox(height: 40),
|
||||
pw.Text("※本書類はシステムにより自動生成され、改ざん防止ハッシュが付与されています。"),
|
||||
pw.Text("※本証憑はSHA-256ハッシュにより改ざん耐性を担保しています。"),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -64,54 +95,83 @@ class _InvoiceAppState extends State<InvoiceApp> {
|
|||
|
||||
final Uint8List bytes = await pdf.save();
|
||||
|
||||
// 2. SHA-256ハッシュ計算
|
||||
final hash = sha256.convert(bytes).toString().substring(0, 10);
|
||||
final fileName =
|
||||
"${DateFormat('yyyyMMdd').format(DateTime.now())}_${total}円_hash_$hash.pdf";
|
||||
// SHA-256ハッシュ計算(末尾8文字)
|
||||
final String fullHash = sha256.convert(bytes).toString();
|
||||
final String hash = fullHash.substring(fullHash.length - 8);
|
||||
|
||||
// 3. 保存
|
||||
final directory = await getExternalStorageDirectory();
|
||||
final file = File("${directory!.path}/$fileName");
|
||||
// ファイル名生成
|
||||
String fileName = filenameTemplate
|
||||
.replaceAll("{date}", dateFileStr)
|
||||
.replaceAll("{name}", clientName)
|
||||
.replaceAll("{amount}", amountFormatter.format(total))
|
||||
.replaceAll("{hash}", hash);
|
||||
|
||||
// 一時保存
|
||||
final directory = await getTemporaryDirectory(); // 共有用なので一時フォルダでOK
|
||||
final file = File("${directory.path}/$fileName");
|
||||
await file.writeAsBytes(bytes);
|
||||
|
||||
// 【新機能】共有メニューを起動!
|
||||
await Share.shareXFiles(
|
||||
[XFile(file.path)],
|
||||
text: '請求書を送付します: $fileName',
|
||||
subject: '請求書: $clientName 様'
|
||||
);
|
||||
|
||||
setState(() {
|
||||
_status = "【発行成功】\nファイル: $fileName\nハッシュ: $hash";
|
||||
_status = "【発行&共有】\n$fileName\nHASH: $hash";
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text("現場のじぇみエモン請求書 V1.0")),
|
||||
appBar: AppBar(
|
||||
title: const Text("現場のじぇみエモン請求書 V1.2"),
|
||||
backgroundColor: Colors.blueGrey,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
TextField(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(children: [
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _clientController,
|
||||
decoration: const InputDecoration(labelText: "取引先名"),
|
||||
decoration: const InputDecoration(labelText: "取引先名", border: OutlineInputBorder())
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.contact_mail, color: Colors.blue, size: 36),
|
||||
onPressed: _pickContact,
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _amountController,
|
||||
decoration: const InputDecoration(labelText: "単価(10%抜き)"),
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(labelText: "単価 (税抜)", border: OutlineInputBorder())
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _generateInvoice,
|
||||
icon: const Icon(Icons.send),
|
||||
label: const Text("PDF発行 & 共有 (Nextcloudへ)"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
),
|
||||
child: const Text("日本語PDF発行(ハッシュ付与)"),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SelectableText(
|
||||
_status,
|
||||
style: const TextStyle(
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold,
|
||||
minimumSize: const Size(double.infinity, 60),
|
||||
backgroundColor: Colors.blueAccent,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 24),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
color: Colors.grey[200],
|
||||
child: SelectableText(_status, style: const TextStyle(fontFamily: 'monospace')),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import share_plus
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.5+2"
|
||||
crypto:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -113,11 +121,27 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_contacts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_contacts
|
||||
sha256: "388d32cd33f16640ee169570128c933b45f3259bddbfae7a100bb49e5ffea9ae"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.9+2"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
|
@ -131,6 +155,11 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -227,6 +256,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
native_toolchain_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -315,6 +352,54 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.11.3"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.1"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.0.1"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.4.7"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3+5"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -363,6 +448,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.1"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
|
@ -424,6 +525,46 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.5"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -440,6 +581,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.2"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.15.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ dependencies:
|
|||
path_provider: ^2.1.5
|
||||
crypto: ^3.0.7
|
||||
intl: ^0.20.2
|
||||
flutter_contacts: ^1.1.9+2
|
||||
permission_handler: ^12.0.1
|
||||
share_plus: ^12.0.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
|||
|
|
@ -6,6 +6,15 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
permission_handler_windows
|
||||
share_plus
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
|
|
|||
Loading…
Reference in a new issue