1 Commits

Author SHA1 Message Date
Henrik Jess Nielsen
0328660589 backup: uncommitted changes from MAC-M9FQ0900T3 2026-05-17 15:52:58
Some checks failed
Flutter CI / analyze-and-test (push) Has been cancelled
2026-05-17 15:52:58 +02:00
49 changed files with 2764 additions and 361 deletions

45
app/.gitignore vendored Normal file
View File

@@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

30
app/.metadata Normal file
View File

@@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "00b0c91f06209d9e4a41f71b7a512d6eb3b9c694"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
- platform: web
create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@@ -0,0 +1,39 @@
package io.flutter.plugins;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine;
/**
* Generated file. Do not edit.
* This file is generated by the Flutter tool based on the
* plugins that support the Android platform.
*/
@Keep
public final class GeneratedPluginRegistrant {
private static final String TAG = "GeneratedPluginRegistrant";
public static void registerWith(@NonNull FlutterEngine flutterEngine) {
try {
flutterEngine.getPlugins().add(new com.lib.flutter_blue_plus.FlutterBluePlusPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin flutter_blue_plus_android, com.lib.flutter_blue_plus.FlutterBluePlusPlugin", e);
}
try {
flutterEngine.getPlugins().add(new com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin flutter_local_notifications, com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin", e);
}
try {
flutterEngine.getPlugins().add(new com.baseflow.permissionhandler.PermissionHandlerPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin permission_handler_android, com.baseflow.permissionhandler.PermissionHandlerPlugin", e);
}
try {
flutterEngine.getPlugins().add(new io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin shared_preferences_android, io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin", e);
}
}
}

View File

@@ -0,0 +1 @@
flutter.sdk=/opt/homebrew/share/flutter

View File

@@ -0,0 +1,14 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/opt/homebrew/share/flutter
FLUTTER_APPLICATION_PATH=/Users/lrihni/Projects/SigHej/app
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_TARGET=lib/main.dart
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=0.1.0
FLUTTER_BUILD_NUMBER=1
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
EXCLUDED_ARCHS[sdk=iphoneos*]=armv7
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.dart_tool/package_config.json

View File

@@ -0,0 +1,32 @@
#
# Generated file, do not edit.
#
import lldb
def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict):
"""Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages."""
base = frame.register["x0"].GetValueAsAddress()
page_len = frame.register["x1"].GetValueAsUnsigned()
# Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the
# first page to see if handled it correctly. This makes diagnosing
# misconfiguration (e.g. missing breakpoint) easier.
data = bytearray(page_len)
data[0:8] = b'IHELPED!'
error = lldb.SBError()
frame.GetThread().GetProcess().WriteMemory(base, data, error)
if not error.Success():
print(f'Failed to write into {base}[+{page_len}]', error)
return
def __lldb_init_module(debugger: lldb.SBDebugger, _):
target = debugger.GetDummyTarget()
# Caveat: must use BreakpointCreateByRegEx here and not
# BreakpointCreateByName. For some reasons callback function does not
# get carried over from dummy target for the later.
bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$")
bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__))
bp.SetAutoContinue(True)
print("-- LLDB integration loaded --")

View File

@@ -0,0 +1,5 @@
#
# Generated file, do not edit.
#
command script import --relative-to-command-file flutter_lldb_helper.py

View File

@@ -0,0 +1,13 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/opt/homebrew/share/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/lrihni/Projects/SigHej/app"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_TARGET=lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=0.1.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.dart_tool/package_config.json"

View File

@@ -0,0 +1,19 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GeneratedPluginRegistrant_h
#define GeneratedPluginRegistrant_h
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN
@interface GeneratedPluginRegistrant : NSObject
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
@end
NS_ASSUME_NONNULL_END
#endif /* GeneratedPluginRegistrant_h */

View File

@@ -0,0 +1,42 @@
//
// Generated file. Do not edit.
//
// clang-format off
#import "GeneratedPluginRegistrant.h"
#if __has_include(<flutter_blue_plus_darwin/FlutterBluePlusPlugin.h>)
#import <flutter_blue_plus_darwin/FlutterBluePlusPlugin.h>
#else
@import flutter_blue_plus_darwin;
#endif
#if __has_include(<flutter_local_notifications/FlutterLocalNotificationsPlugin.h>)
#import <flutter_local_notifications/FlutterLocalNotificationsPlugin.h>
#else
@import flutter_local_notifications;
#endif
#if __has_include(<permission_handler_apple/PermissionHandlerPlugin.h>)
#import <permission_handler_apple/PermissionHandlerPlugin.h>
#else
@import permission_handler_apple;
#endif
#if __has_include(<shared_preferences_foundation/SharedPreferencesPlugin.h>)
#import <shared_preferences_foundation/SharedPreferencesPlugin.h>
#else
@import shared_preferences_foundation;
#endif
@implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
[FlutterBluePlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterBluePlusPlugin"]];
[FlutterLocalNotificationsPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterLocalNotificationsPlugin"]];
[PermissionHandlerPlugin registerWithRegistrar:[registry registrarForPlugin:@"PermissionHandlerPlugin"]];
[SharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"SharedPreferencesPlugin"]];
}
@end

View File

@@ -0,0 +1,115 @@
/// Interesse-specifikke conversation starters.
/// Vist i nudge-kortet når et match opdages.
const Map<String, List<String>> kConversationStarters = {
'Tech': [
'Hvad arbejder du på for tiden?',
'Hvad er du mest begejstret for inden for tech lige nu?',
'Er du mere frontend eller backend?',
],
'AI': [
'Hvad bruger du AI til i hverdagen?',
'Har du prøvet noget nyt inden for AI for nylig?',
'Hvad synes du om retningen AI bevæger sig i?',
],
'Musik': [
'Hvad lytter du til for tiden?',
'Har du været til en god koncert for nylig?',
'Hvad er din go-to genre når du skal fokusere?',
],
'Sport': [
'Hvad træner du hen imod?',
'Hvad er din favoritsport at følge?',
'Motionerer du mest alene eller med andre?',
],
'Løb': [
'Hvad er din næste løbsbegivenhed?',
'Løber du mest om morgenen eller om aftenen?',
'Hvad er det bedste løbetip du har fået?',
],
'Cykling': [
'Pendler du på cykel?',
'Har du en drømmerute du gerne vil cykle?',
'Road bike eller mountainbike?',
],
'Yoga': [
'Hvilken stil yoga foretrækker du?',
'Hvornår på dagen praktiserer du helst?',
'Hvad fik dig til at starte med yoga?',
],
'Madlavning': [
'Hvad har du lavet i køkkenet for nylig?',
'Er der et køkken du er særligt glad for?',
'Hvad er din bedste quick-win opskrift?',
],
'Rejser': [
'Hvad er det seneste sted du har besøgt?',
'Har du noget på rejse-ønskesedlen?',
'Foretrækker du by eller natur når du rejser?',
],
'Bøger': [
'Hvad læser du i øjeblikket?',
'Hvad er den bedste bog du har læst det seneste år?',
'Fiktion eller non-fiktion?',
],
'Film': [
'Hvad har du set for nylig der overraskede dig?',
'Hvad er din favoritgenre?',
'Streamer du eller går du til biograf?',
],
'Gaming': [
'Hvad spiller du mest for tiden?',
'PC, konsol eller mobil?',
'Hvad er det spil du har brugt flest timer på?',
],
'Design': [
'Hvad arbejder du med inden for design?',
'Hvad inspirerer dig visuelt for tiden?',
'Digitalt eller analogt — hvad er dit udgangspunkt?',
],
'Foto': [
'Hvad fotograferer du mest?',
'Hvad er dit yndlingsudstyr?',
'Film eller digital?',
],
'Kunst': [
'Hvad er du i gang med at skabe?',
'Hvad er dit foretrukne medie?',
'Er der noget du har set for nylig der inspirerede dig?',
],
'Klima': [
'Hvad gør du selv der giver størst effekt?',
'Hvad er du mest optimistisk omkring inden for klima?',
'Følger du med i en bestemt vinkel af klimadebatten?',
],
'Filosofi': [
'Hvad er du optaget af at tænke over for tiden?',
'Er der en filosof du vender tilbage til igen og igen?',
'Hvad er det vigtigste spørgsmål ingen kan svare på?',
],
'Iværksætteri': [
'Arbejder du på noget selv for øjeblikket?',
'Hvad er den vigtigste ting du har lært som iværksætter?',
'Hvad er din største udfordring lige nu?',
],
'Meditation': [
'Hvilken form for meditation praktiserer du?',
'Hvornår på dagen fungerer det bedst for dig?',
'Hvad fik dig til at starte?',
],
'Sprog': [
'Hvilke sprog taler du?',
'Hvad er det sværeste ved at lære et nyt sprog?',
'Lærer du et nyt sprog i øjeblikket?',
],
};
/// Returnerer en tilfældig conversation starter for en given interesse.
/// Falder tilbage til en generisk starter hvis interessen ikke kendes.
String getStarter(String interest) {
final starters = kConversationStarters[interest];
if (starters == null || starters.isEmpty) {
return 'Hvad arbejder du med for tiden?';
}
final copy = List<String>.from(starters)..shuffle();
return copy.first;
}

View File

@@ -25,11 +25,13 @@ class SigHejApp extends StatelessWidget {
theme: buildTheme(), theme: buildTheme(),
darkTheme: buildTheme(dark: true), darkTheme: buildTheme(dark: true),
themeMode: ThemeMode.system, themeMode: ThemeMode.system,
home: Consumer<SessionStore>( routes: {
'/': (context) => Consumer<SessionStore>(
builder: (context, store, _) => store.hasProfile builder: (context, store, _) => store.hasProfile
? const HomeScreen() ? const HomeScreen()
: const ProfileScreen(isSetup: true), : const ProfileScreen(isSetup: true),
), ),
},
); );
} }
} }

View File

@@ -1,214 +1,303 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:sighej/data/conversation_starters.dart';
import 'package:sighej/screens/profile_screen.dart'; import 'package:sighej/screens/profile_screen.dart';
import 'package:sighej/services/api_service.dart'; import 'package:sighej/services/api_service.dart';
import 'package:sighej/services/session_store.dart'; import 'package:sighej/services/session_store.dart';
// BLE service UUID that identifies SigHej devices. bool get _bleAvailable =>
const String kSigHejServiceUuid = '1248f5a0-0000-1000-8000-00805f9b34fb'; !kIsWeb &&
(defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS);
// Manufacturer ID used in BLE advertising data (Android). const kSigHejServiceUuid = '1248f5a0-0000-1000-8000-00805f9b34fb';
const int kManufacturerId = 0x4E58; // "NX" const kManufacturerId = 0x4E58;
final FlutterLocalNotificationsPlugin _notifications =
FlutterLocalNotificationsPlugin();
const _androidChannel = AndroidNotificationDetails(
'sighej_nudge',
'SigHej Nudges',
channelDescription: 'Notifies when a nearby person shares your interests',
importance: Importance.high,
priority: Priority.high,
icon: '@mipmap/ic_launcher',
);
class HomeScreen extends StatefulWidget { class HomeScreen extends StatefulWidget {
const HomeScreen({super.key}); const HomeScreen({super.key});
@override @override
State<HomeScreen> createState() => _HomeScreenState(); State<HomeScreen> createState() => _HomeScreenState();
} }
class _HomeScreenState extends State<HomeScreen> { class _HomeScreenState extends State<HomeScreen>
with SingleTickerProviderStateMixin {
bool _openToTalk = false; bool _openToTalk = false;
String? _lastNudge; String? _lastNudge;
StreamSubscription? _bleSub; String? _lastStarter;
late final AnimationController _pulseCtrl;
// Track tokens we have already matched this session to avoid spam. late final Animation<double> _pulseAnim;
final Set<String> _matchedTokens = {};
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initNotifications(); _pulseCtrl = AnimationController(
} vsync: this,
duration: const Duration(seconds: 2),
Future<void> _initNotifications() async {
const android = AndroidInitializationSettings('@mipmap/ic_launcher');
const ios = DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
); );
await _notifications.initialize( _pulseAnim = Tween<double>(begin: 1.0, end: 1.12).animate(
const InitializationSettings(android: android, iOS: ios), CurvedAnimation(parent: _pulseCtrl, curve: Curves.easeInOut),
); );
} }
@override @override
void dispose() { void dispose() {
_stopScanning(); _pulseCtrl.dispose();
super.dispose(); super.dispose();
} }
Future<void> _toggle(SessionStore store) async { Future<void> _toggle(SessionStore store) async {
if (_openToTalk) { if (_openToTalk) {
_stopScanning(); _pulseCtrl.stop();
return; _pulseCtrl.reset();
}
final granted = await _requestPermissions();
if (!granted) return;
await registerSession(
store.bleToken,
store.interests,
name: store.name,
tagline: store.tagline,
);
await _startAdvertising(store.bleToken);
FlutterBluePlus.startScan(timeout: const Duration(minutes: 120));
_bleSub = FlutterBluePlus.onScanResults.listen((results) async {
for (final r in results) {
final detected = _parseSigHejToken(r);
if (detected == null ||
detected == store.bleToken ||
_matchedTokens.contains(detected)) {
continue;
}
_matchedTokens.add(detected);
await _handlePotentialMatch(store.bleToken, detected, store);
}
});
setState(() => _openToTalk = true);
}
Future<void> _startAdvertising(String token) async {
final tokenBytes = Uint8List.fromList(token.codeUnits);
try {
await FlutterBluePlus.startAdvertising(
AdvertiseData(
serviceUuids: [Guid(kSigHejServiceUuid)],
manufacturerData: [ManufacturerData(kManufacturerId, tokenBytes)],
),
);
} catch (e) {
debugPrint('BLE advertise error: $e');
}
}
void _stopScanning() {
FlutterBluePlus.stopScan();
FlutterBluePlus.stopAdvertising();
_bleSub?.cancel();
_bleSub = null;
_matchedTokens.clear();
setState(() { setState(() {
_openToTalk = false; _openToTalk = false;
_lastNudge = null; _lastNudge = null;
_lastStarter = null;
});
return;
}
try {
await registerSession(store.bleToken, store.interests,
name: store.name, tagline: store.tagline);
} catch (_) {}
_pulseCtrl.repeat(reverse: true);
setState(() => _openToTalk = true);
if (!_bleAvailable) {
await Future.delayed(const Duration(seconds: 3));
if (mounted && _openToTalk) {
// Demo: pick a random interest from the user's list
final interests = store.interests;
final matchedInterest =
interests.isNotEmpty ? (interests..shuffle()).first : 'Tech';
setState(() {
_lastNudge =
'Nogen i nærheden deler din interesse for $matchedInterest';
_lastStarter = getStarter(matchedInterest);
}); });
} }
Future<void> _handlePotentialMatch(
String own, String detected, SessionStore store) async {
try {
final result = await reportMatch(own, detected);
if (result == null || !result.match) return;
final interests = result.sharedInterests;
final body = interests.isEmpty
? '${store.displayName} — nogen i nærheden er åben for en snak!'
: 'Fælles interesser: ${interests.join(', ')}';
await _showNudgeNotification('SigHej — sig hej!', body);
setState(() => _lastNudge = body);
} catch (e) {
debugPrint('Match error: $e');
} }
} }
Future<void> _showNudgeNotification(String title, String body) async {
const details = NotificationDetails(android: _androidChannel);
await _notifications.show(
body.hashCode,
title,
body,
details,
);
}
/// Parses a SigHej BLE token from a scan result.
/// Android: reads manufacturer data with key [kManufacturerId].
/// iOS fallback: reads service data for [kSigHejServiceUuid].
String? _parseSigHejToken(ScanResult result) {
final ad = result.advertisementData;
// Filter: only process SigHej advertisers.
final hasSigHejUuid = ad.serviceUuids
.map((g) => g.toString().toLowerCase())
.contains(kSigHejServiceUuid.toLowerCase());
if (!hasSigHejUuid) return null;
// Android: manufacturer data.
final mfr = ad.manufacturerData[kManufacturerId];
if (mfr != null && mfr.isNotEmpty) {
try {
return String.fromCharCodes(mfr);
} catch (_) {}
}
// iOS fallback: service data.
final svcData = ad.serviceData[Guid(kSigHejServiceUuid)];
if (svcData != null && svcData.isNotEmpty) {
try {
return String.fromCharCodes(svcData);
} catch (_) {}
}
return null;
}
Future<bool> _requestPermissions() async {
final perms = [
Permission.bluetooth,
Permission.bluetoothScan,
Permission.bluetoothAdvertise,
Permission.bluetoothConnect,
Permission.location,
Permission.notification,
];
final statuses = await perms.request();
return statuses.values.every((s) => s.isGranted || s.isLimited);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final store = context.watch<SessionStore>(); final store = context.watch<SessionStore>();
final cs = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
return Scaffold( return Scaffold(
appBar: AppBar( backgroundColor: cs.surface,
title: const Text('SigHej'), body: Center(
actions: [ child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 440),
child: Column(
children: [
// ── Top bar ──────────────────────────────────────────
_TopBar(store: store),
// ── Main area ────────────────────────────────────────
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(flex: 2),
// Status text
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Text(
_openToTalk
? 'Du er åben for en snak'
: 'Tryk for at åbne op',
key: ValueKey(_openToTalk),
style: Theme.of(context)
.textTheme
.headlineSmall
?.copyWith(
fontWeight: FontWeight.w700,
color: cs.onSurface),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 8),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Text(
_openToTalk
? 'Søger efter folk med fælles interesser…'
: 'Lad andre vide du er klar til en samtale',
key: ValueKey('sub$_openToTalk'),
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(color: cs.onSurfaceVariant),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 48),
// ── Big toggle button ─────────────────────
GestureDetector(
onTap: () => _toggle(store),
child: ScaleTransition(
scale: _openToTalk ? _pulseAnim : const AlwaysStoppedAnimation(1.0),
child: Stack(
alignment: Alignment.center,
children: [
// Outer glow ring
if (_openToTalk)
Container(
width: 200,
height: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: cs.primary.withAlpha(30),
),
),
// Main circle
Container(
width: 168,
height: 168,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: _openToTalk
? LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
cs.primary,
Color.lerp(cs.primary,
cs.secondary, 0.5)!,
],
)
: LinearGradient(
colors: [
cs.surfaceContainerHighest,
cs.surfaceContainerHighest,
],
),
boxShadow: _openToTalk
? [
BoxShadow(
color: cs.primary.withAlpha(100),
blurRadius: 32,
spreadRadius: 4,
)
]
: [
BoxShadow(
color: isDark
? Colors.black45
: Colors.black12,
blurRadius: 16,
offset: const Offset(0, 4),
)
],
),
child: Icon(
_openToTalk
? Icons.waving_hand_rounded
: Icons.waving_hand_outlined,
size: 68,
color: _openToTalk
? cs.onPrimary
: cs.onSurfaceVariant,
),
),
],
),
),
),
const SizedBox(height: 40),
// ── Nudge card ────────────────────────────
AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
child: _lastNudge != null
? _NudgeCard(
message: _lastNudge!,
starter: _lastStarter,
)
: const SizedBox(height: 80),
),
const Spacer(flex: 3),
// ── Interest chips ────────────────────────
if (store.interests.isNotEmpty)
_InterestRow(interests: store.interests),
const SizedBox(height: 32),
],
),
),
),
],
),
),
),
);
}
}
class _TopBar extends StatelessWidget {
final SessionStore store;
const _TopBar({required this.store});
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final top = MediaQuery.of(context).padding.top;
return Container(
padding: EdgeInsets.fromLTRB(20, top + 12, 12, 16),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isDark
? [const Color(0xFF1A3A40), const Color(0xFF0F2930)]
: [const Color(0xFF2A9D8F), const Color(0xFF264653)],
),
),
child: Row(
children: [
const Icon(Icons.waving_hand, color: Colors.white, size: 22),
const SizedBox(width: 8),
const Text('SigHej',
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w800,
letterSpacing: -0.5)),
const Spacer(),
// Display name chip
Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.white.withAlpha(25),
borderRadius: BorderRadius.circular(20),
),
child: Text(
store.displayName,
style: const TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.w600),
),
),
const SizedBox(width: 4),
IconButton( IconButton(
icon: const Icon(Icons.person_outline), icon: const Icon(Icons.person_outline, color: Colors.white),
onPressed: () => Navigator.of(context).push( onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const ProfileScreen()), MaterialPageRoute(builder: (_) => const ProfileScreen()),
), ),
@@ -216,51 +305,130 @@ class _HomeScreenState extends State<HomeScreen> {
), ),
], ],
), ),
body: Center( );
child: Column( }
mainAxisAlignment: MainAxisAlignment.center, }
children: [
Text( class _NudgeCard extends StatelessWidget {
_openToTalk ? 'You\'re open to talk' : 'Tap to open up', final String message;
style: Theme.of(context).textTheme.headlineSmall, final String? starter;
), const _NudgeCard({required this.message, this.starter});
const SizedBox(height: 32),
GestureDetector( @override
onTap: () => _toggle(store), Widget build(BuildContext context) {
child: AnimatedContainer( final cs = Theme.of(context).colorScheme;
duration: const Duration(milliseconds: 300), return Container(
width: 150, width: double.infinity,
height: 150, padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, gradient: LinearGradient(
color: _openToTalk colors: [cs.primaryContainer, cs.tertiaryContainer],
? Theme.of(context).colorScheme.primary begin: Alignment.topLeft,
: Theme.of(context).colorScheme.surfaceContainerHighest, end: Alignment.bottomRight,
), ),
child: Icon( borderRadius: BorderRadius.circular(20),
_openToTalk ? Icons.record_voice_over : Icons.mic_off, boxShadow: [
size: 60, BoxShadow(
color: _openToTalk color: cs.primary.withAlpha(40),
? Theme.of(context).colorScheme.onPrimary blurRadius: 16,
: Theme.of(context).colorScheme.onSurfaceVariant, offset: const Offset(0, 4),
), ),
],
), ),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: cs.primary.withAlpha(30),
borderRadius: BorderRadius.circular(12),
), ),
if (_lastNudge != null) ...[ child:
const SizedBox(height: 40), Icon(Icons.handshake_outlined, color: cs.primary, size: 24),
Card( ),
margin: const EdgeInsets.symmetric(horizontal: 24), const SizedBox(width: 14),
child: Padding( Expanded(
padding: const EdgeInsets.all(16), child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Nogen i nærheden! 👋',
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 14,
color: cs.onPrimaryContainer)),
const SizedBox(height: 3),
Text(message,
style: TextStyle(
fontSize: 13,
color: cs.onPrimaryContainer.withAlpha(200))),
if (starter != null) ...[
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 7),
decoration: BoxDecoration(
color: cs.primary.withAlpha(20),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: cs.primary.withAlpha(60), width: 1),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('💬 ',
style: TextStyle(fontSize: 13)),
Expanded(
child: Text( child: Text(
_lastNudge!, '"$starter"',
textAlign: TextAlign.center, style: TextStyle(
style: Theme.of(context).textTheme.bodyLarge, fontSize: 13,
fontStyle: FontStyle.italic,
color: cs.onPrimaryContainer,
height: 1.4,
), ),
), ),
), ),
], ],
),
),
], ],
],
),
),
],
),
);
}
}
class _InterestRow extends StatelessWidget {
final List<String> interests;
const _InterestRow({required this.interests});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return SizedBox(
height: 32,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: interests.length,
separatorBuilder: (_, __) => const SizedBox(width: 6),
itemBuilder: (_, i) => Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: cs.secondaryContainer,
borderRadius: BorderRadius.circular(16),
),
child: Text(
interests[i],
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: cs.onSecondaryContainer),
),
), ),
), ),
); );

View File

@@ -2,34 +2,16 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:sighej/services/session_store.dart'; import 'package:sighej/services/session_store.dart';
const List<String> kAvailableInterests = [ const kAvailableInterests = [
'Tech', 'Tech', 'Musik', 'Filosofi', 'Design', 'DevOps', 'Bøger', 'Gaming',
'Musik', 'Fitness', 'Kunst', 'Mad', 'Rejser', 'Videnskab', 'Iværksætteri',
'Filosofi', 'Film', 'Natur', 'Kodning', 'Podcast', 'Arkitektur', 'Klima', 'Sport',
'Design',
'DevOps',
'Bøger',
'Gaming',
'Fitness',
'Kunst',
'Mad',
'Rejser',
'Videnskab',
'Iværksætteri',
'Film',
'Natur',
'Kodning',
'Podcast',
'Arkitektur',
'Klima',
'Sport',
]; ];
class ProfileScreen extends StatefulWidget { class ProfileScreen extends StatefulWidget {
/// If [isSetup] is true, the screen is shown as first-run onboarding. /// [isSetup] = true first-run onboarding (no back button)
/// If false, it's opened from the home screen as "rediger profil". /// [isSetup] = false → edit mode from HomeScreen
final bool isSetup; final bool isSetup;
const ProfileScreen({super.key, this.isSetup = false}); const ProfileScreen({super.key, this.isSetup = false});
@override @override
@@ -37,17 +19,22 @@ class ProfileScreen extends StatefulWidget {
} }
class _ProfileScreenState extends State<ProfileScreen> { class _ProfileScreenState extends State<ProfileScreen> {
late final TextEditingController _nameCtrl; final _nameCtrl = TextEditingController();
late final TextEditingController _taglineCtrl; final _taglineCtrl = TextEditingController();
late final Set<String> _selected; final Set<String> _selected = {};
bool _triedSubmit = false;
bool _loaded = false;
@override @override
void initState() { void didChangeDependencies() {
super.initState(); super.didChangeDependencies();
if (!_loaded) {
final store = context.read<SessionStore>(); final store = context.read<SessionStore>();
_nameCtrl = TextEditingController(text: store.name); _nameCtrl.text = store.name;
_taglineCtrl = TextEditingController(text: store.tagline); _taglineCtrl.text = store.tagline;
_selected = Set<String>.from(store.interests); _selected.addAll(store.interests);
_loaded = true;
}
} }
@override @override
@@ -58,144 +45,337 @@ class _ProfileScreenState extends State<ProfileScreen> {
} }
Future<void> _save() async { Future<void> _save() async {
await context.read<SessionStore>().saveProfile( setState(() => _triedSubmit = true);
if (_selected.isEmpty) return;
final store = context.read<SessionStore>();
await store.saveProfile(
name: _nameCtrl.text.trim(), name: _nameCtrl.text.trim(),
tagline: _taglineCtrl.text.trim(), tagline: _taglineCtrl.text.trim(),
interests: _selected.toList(), interests: _selected.toList(),
); );
if (mounted) Navigator.of(context).pop(); if (!mounted) return;
if (widget.isSetup) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const _HomeRedirect()),
);
} else {
Navigator.of(context).pop();
}
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isSetup = widget.isSetup; final cs = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
appBar: AppBar( backgroundColor: cs.surface,
title: Text(isSetup ? 'Hvem er du?' : 'Din profil'), body: Center(
automaticallyImplyLeading: !isSetup, child: ConstrainedBox(
), constraints: const BoxConstraints(maxWidth: 440),
body: SafeArea(
child: Column( child: Column(
children: [ children: [
// ── Gradient header ──────────────────────────────────
_Header(isSetup: widget.isSetup),
// ── Scrollable form body ─────────────────────────────
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (isSetup) ...[ _buildTextField(
Text( context,
'SigHej sender en diskret notifikation, når nogen i nærheden deler dine interesser. ' label: 'Kaldenavn',
'Ingen profiler at swipe — bare et lille vink om at starte en samtale.', hint: 'F.eks. "Henrik" eller "Tech-nerd"',
style: Theme.of(context).textTheme.bodyMedium?.copyWith( sublabel: 'Valgfrit',
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 28),
],
_SectionLabel('Kaldenavn', hint: 'Valgfrit — hvad vil du kaldes?'),
const SizedBox(height: 8),
TextField(
controller: _nameCtrl, controller: _nameCtrl,
maxLength: 40, maxLength: 40,
textCapitalization: TextCapitalization.sentences,
decoration: const InputDecoration(
hintText: 'F.eks. "Henrik" eller "Tech-nerd"',
border: OutlineInputBorder(),
counterText: '',
), ),
), const SizedBox(height: 20),
const SizedBox(height: 24), _buildTextField(
_SectionLabel( context,
'Hvad er du op til i dag?', label: 'Hvad er du op til i dag?',
hint: 'Valgfrit — sæt tonen', hint: '"Åben for en kaffesnak" eller "Klar til at netværke"',
), sublabel: 'Valgfrit — sæt tonen',
const SizedBox(height: 8),
TextField(
controller: _taglineCtrl, controller: _taglineCtrl,
maxLength: 80, maxLength: 80,
textCapitalization: TextCapitalization.sentences, ),
decoration: const InputDecoration( const SizedBox(height: 28),
hintText: 'F.eks. "Åben for en kaffesnak" eller "Op til at netværke"', Row(children: [
border: OutlineInputBorder(), Text('Interesser',
counterText: '', style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(
fontWeight: FontWeight.w700,
color: cs.onSurface)),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: _selected.isEmpty && _triedSubmit
? cs.errorContainer
: cs.primaryContainer,
borderRadius: BorderRadius.circular(20),
),
child: Text(
'${_selected.length} valgt',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: _selected.isEmpty && _triedSubmit
? cs.onErrorContainer
: cs.onPrimaryContainer,
), ),
), ),
const SizedBox(height: 24),
_SectionLabel(
'Hvad interesserer dig?',
hint: 'Vi finder folk med fælles interesser i nærheden',
), ),
]),
const SizedBox(height: 4),
Text('Vælg mindst én',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: cs.onSurfaceVariant)),
const SizedBox(height: 12), const SizedBox(height: 12),
Wrap( Wrap(
spacing: 8, spacing: 8,
runSpacing: 8, runSpacing: 8,
children: kAvailableInterests.map((interest) { children: kAvailableInterests.map((tag) {
final selected = _selected.contains(interest); final on = _selected.contains(tag);
return FilterChip( return _InterestChip(
label: Text(interest), label: tag,
selected: selected, selected: on,
onSelected: (val) => setState( onTap: () => setState(() =>
() => val on ? _selected.remove(tag) : _selected.add(tag)),
? _selected.add(interest)
: _selected.remove(interest),
),
); );
}).toList(), }).toList(),
), ),
const SizedBox(height: 16), if (_triedSubmit && _selected.isEmpty) ...[
if (_selected.isEmpty) const SizedBox(height: 8),
Text( Text('Vælg mindst ét emne for at bruge SigHej.',
'Vælg mindst ét emne for at bruge SigHej.',
style: TextStyle( style: TextStyle(
color: Theme.of(context).colorScheme.error, color: cs.error, fontSize: 12)),
fontSize: 13, ],
), const SizedBox(height: 32),
),
], ],
), ),
), ),
), ),
// ── Save button ──────────────────────────────────────
Padding( Padding(
padding: const EdgeInsets.fromLTRB(24, 8, 24, 24), padding: const EdgeInsets.fromLTRB(24, 12, 24, 32),
child: SizedBox(
width: double.infinity,
height: 54,
child: FilledButton( child: FilledButton(
onPressed: _selected.isEmpty ? null : _save, onPressed: _save,
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(52), backgroundColor: cs.primary,
foregroundColor: cs.onPrimary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
),
child: Text(
widget.isSetup ? 'Kom i gang →' : 'Gem profil',
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.w700),
),
), ),
child: Text(isSetup ? 'Kom i gang' : 'Gem'),
), ),
), ),
], ],
), ),
), ),
),
); );
} }
}
class _SectionLabel extends StatelessWidget { Widget _buildTextField(
final String label; BuildContext context, {
final String? hint; required String label,
required String hint,
const _SectionLabel(this.label, {this.hint}); required String sublabel,
required TextEditingController controller,
@override required int maxLength,
Widget build(BuildContext context) { }) {
final cs = Theme.of(context).colorScheme;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(label, style: Theme.of(context).textTheme.titleSmall), Text(label,
if (hint != null) ...[ style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700, color: cs.onSurface)),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(sublabel,
hint!, style: Theme.of(context)
style: Theme.of(context).textTheme.bodySmall?.copyWith( .textTheme
color: Theme.of(context).colorScheme.onSurfaceVariant, .bodySmall
?.copyWith(color: cs.onSurfaceVariant)),
const SizedBox(height: 8),
TextField(
controller: controller,
maxLength: maxLength,
textCapitalization: TextCapitalization.sentences,
style: TextStyle(color: cs.onSurface),
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(color: cs.onSurfaceVariant.withAlpha(160)),
counterText: '',
filled: true,
fillColor: cs.surfaceContainerHighest.withAlpha(120),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: cs.outline.withAlpha(80)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: cs.outline.withAlpha(80)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: cs.primary, width: 2),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
), ),
), ),
], ],
],
); );
} }
} }
class _Header extends StatelessWidget {
final bool isSetup;
const _Header({required this.isSetup});
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Container(
width: double.infinity,
padding: EdgeInsets.fromLTRB(
24, MediaQuery.of(context).padding.top + 32, 24, 32),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isDark
? [const Color(0xFF1A3A40), const Color(0xFF0F2930)]
: [const Color(0xFF2A9D8F), const Color(0xFF264653)],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isSetup)
IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.of(context).pop(),
padding: EdgeInsets.zero,
),
Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: Colors.white.withAlpha(30),
borderRadius: BorderRadius.circular(14),
),
child: const Icon(Icons.waving_hand,
color: Colors.white, size: 26),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('SigHej',
style: TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.w800,
letterSpacing: -0.5)),
Text(
isSetup ? 'Fortæl lidt om dig selv' : 'Rediger profil',
style: TextStyle(
color: Colors.white.withAlpha(180), fontSize: 13),
),
],
),
],
),
if (isSetup) ...[
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white.withAlpha(20),
borderRadius: BorderRadius.circular(12),
),
child: Text(
'SigHej sender en diskret notifikation, når nogen i nærheden deler dine interesser. Ingen profiler at swipe — bare et lille vink om at starte en samtale.',
style: TextStyle(
color: Colors.white.withAlpha(210),
fontSize: 13,
height: 1.5),
),
),
],
],
),
);
}
}
class _InterestChip extends StatelessWidget {
final String label;
final bool selected;
final VoidCallback onTap;
const _InterestChip(
{required this.label, required this.selected, required this.onTap});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 180),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
color: selected ? cs.primary : cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: selected ? cs.primary : cs.outline.withAlpha(60),
width: selected ? 0 : 1,
),
),
child: Text(
label,
style: TextStyle(
fontSize: 13,
fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
color: selected ? cs.onPrimary : cs.onSurface,
),
),
),
);
}
}
// Redirects to HomeScreen after profile save — avoids circular import.
class _HomeRedirect extends StatelessWidget {
const _HomeRedirect();
@override
Widget build(BuildContext context) {
// Defer to main.dart routing which will now see hasProfile=true.
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pushNamedAndRemoveUntil('/', (_) => false);
});
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
}

View File

@@ -1,5 +1,3 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';

7
app/macos/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
# Flutter-related
**/Flutter/ephemeral/
**/Pods/
# Xcode-related
**/dgph
**/xcuserdata/

View File

@@ -0,0 +1 @@
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@@ -0,0 +1 @@
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@@ -0,0 +1,16 @@
//
// Generated file. Do not edit.
//
import FlutterMacOS
import Foundation
import flutter_blue_plus_darwin
import flutter_local_notifications
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}

View File

@@ -0,0 +1,705 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
buildPhases = (
33CC111E2044C6BF0003C045 /* ShellScript */,
);
dependencies = (
);
name = "Flutter Assemble";
productName = FLX;
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC10EC2044A3C60003C045;
remoteInfo = Runner;
};
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
remoteInfo = FLX;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
33CC110E2044A8840003C045 /* Bundle Framework */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Bundle Framework";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* sighej.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "sighej.app"; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
331C80D2294CF70F00263BE5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EA2044A3C60003C045 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C80D6294CF71000263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C80D7294CF71000263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
33BA886A226E78AF003329D5 /* Configs */ = {
isa = PBXGroup;
children = (
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
);
path = Configs;
sourceTree = "<group>";
};
33CC10E42044A3C60003C045 = {
isa = PBXGroup;
children = (
33FAB671232836740065AC1E /* Runner */,
33CEB47122A05771004F2AC0 /* Flutter */,
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
);
sourceTree = "<group>";
};
33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* sighej.app */,
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
33CC11242044D66E0003C045 /* Resources */ = {
isa = PBXGroup;
children = (
33CC10F22044A3C60003C045 /* Assets.xcassets */,
33CC10F42044A3C60003C045 /* MainMenu.xib */,
33CC10F72044A3C60003C045 /* Info.plist */,
);
name = Resources;
path = ..;
sourceTree = "<group>";
};
33CEB47122A05771004F2AC0 /* Flutter */ = {
isa = PBXGroup;
children = (
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
);
path = Flutter;
sourceTree = "<group>";
};
33FAB671232836740065AC1E /* Runner */ = {
isa = PBXGroup;
children = (
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
33E51914231749380026EE4D /* Release.entitlements */,
33CC11242044D66E0003C045 /* Resources */,
33BA886A226E78AF003329D5 /* Configs */,
);
path = Runner;
sourceTree = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C80D4294CF70F00263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C80DA294CF71000263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
33CC10EC2044A3C60003C045 /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
);
buildRules = (
);
dependencies = (
33CC11202044C79F0003C045 /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* sighej.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
33CC10E52044A3C60003C045 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 33CC10EC2044A3C60003C045;
};
33CC10EC2044A3C60003C045 = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Sandbox = {
enabled = 1;
};
};
};
33CC111A2044C6BA0003C045 = {
CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Manual;
};
};
};
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 33CC10E42044A3C60003C045;
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
33CC10EC2044A3C60003C045 /* Runner */,
331C80D4294CF70F00263BE5 /* RunnerTests */,
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C80D3294CF70F00263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EB2044A3C60003C045 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
};
33CC111E2044C6BF0003C045 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
Flutter/ephemeral/FlutterInputs.xcfilelist,
);
inputPaths = (
Flutter/ephemeral/tripwire,
);
outputFileListPaths = (
Flutter/ephemeral/FlutterOutputs.xcfilelist,
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C80D1294CF70F00263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10E92044A3C60003C045 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC10EC2044A3C60003C045 /* Runner */;
targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
};
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
33CC10F52044A3C60003C045 /* Base */,
);
name = MainMenu.xib;
path = Runner;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.sighej.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/sighej.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/sighej";
};
name = Debug;
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.sighej.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/sighej.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/sighej";
};
name = Release;
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.sighej.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/sighej.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/sighej";
};
name = Profile;
};
338D0CE9231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Profile;
};
338D0CEA231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Profile;
};
338D0CEB231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Profile;
};
33CC10F92044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
33CC10FA2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
33CC10FC2044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
33CC10FD2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Release;
};
33CC111C2044C6BA0003C045 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
33CC111D2044C6BA0003C045 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C80DB294CF71000263BE5 /* Debug */,
331C80DC294CF71000263BE5 /* Release */,
331C80DD294CF71000263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10F92044A3C60003C045 /* Debug */,
33CC10FA2044A3C60003C045 /* Release */,
338D0CE9231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10FC2044A3C60003C045 /* Debug */,
33CC10FD2044A3C60003C045 /* Release */,
338D0CEA231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC111C2044C6BA0003C045 /* Debug */,
33CC111D2044C6BA0003C045 /* Release */,
338D0CEB231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "sighej.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "sighej.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C80D4294CF70F00263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "sighej.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "sighej.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,13 @@
import Cocoa
import FlutterMacOS
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}

View File

@@ -0,0 +1,68 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,343 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
<connections>
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="APP_NAME" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About APP_NAME" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="aUF-d1-5bR">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
<connections>
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="R4o-n2-Eq4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="EPT-qC-fAb">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="rJ0-wn-3NY"/>
</menuItem>
</items>
<point key="canvasLocation" x="142" y="-258"/>
</menu>
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask"/>
</view>
</window>
</objects>
</document>

View File

@@ -0,0 +1,14 @@
// Application-level settings for the Runner target.
//
// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
// future. If not, the values below would default to using the project name when this becomes a
// 'flutter create' template.
// The application's name. By default this is also the title of the Flutter window.
PRODUCT_NAME = sighej
// The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = com.example.sighej
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved.

View File

@@ -0,0 +1,2 @@
#include "../../Flutter/Flutter-Debug.xcconfig"
#include "Warnings.xcconfig"

View File

@@ -0,0 +1,2 @@
#include "../../Flutter/Flutter-Release.xcconfig"
#include "Warnings.xcconfig"

View File

@@ -0,0 +1,13 @@
WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
GCC_WARN_UNDECLARED_SELECTOR = YES
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
CLANG_WARN_PRAGMA_PACK = YES
CLANG_WARN_STRICT_PROTOTYPES = YES
CLANG_WARN_COMMA = YES
GCC_WARN_STRICT_SELECTOR_MATCH = YES
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
GCC_WARN_SHADOW = YES
CLANG_WARN_UNREACHABLE_CODE = YES

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>$(PRODUCT_COPYRIGHT)</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@@ -0,0 +1,15 @@
import Cocoa
import FlutterMacOS
class MainFlutterWindow: NSWindow {
override func awakeFromNib() {
let flutterViewController = FlutterViewController()
let windowFrame = self.frame
self.contentViewController = flutterViewController
self.setFrame(windowFrame, display: true)
RegisterGeneratedPlugins(registry: flutterViewController)
super.awakeFromNib()
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,12 @@
import Cocoa
import FlutterMacOS
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

18
app/test/widget_test.dart Normal file
View File

@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:sighej/main.dart';
import 'package:sighej/services/session_store.dart';
void main() {
testWidgets('App renders SigHej title', (WidgetTester tester) async {
await tester.pumpWidget(
ChangeNotifierProvider(
create: (_) => SessionStore(),
child: const SigHejApp(),
),
);
// Profile screen shows on first run — check it loads without crash.
expect(find.byType(MaterialApp), findsOneWidget);
});
}

BIN
app/web/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

46
app/web/index.html Normal file
View File

@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="sighej">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>sighej</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<!--
You can customize the "flutter_bootstrap.js" script.
This is useful to provide a custom configuration to the Flutter loader
or to give the user feedback during the initialization process.
For more details:
* https://docs.flutter.dev/platform-integration/web/initialization
-->
<script src="flutter_bootstrap.js" async></script>
</body>
</html>

35
app/web/manifest.json Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "sighej",
"short_name": "sighej",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

2
note Normal file
View File

@@ -0,0 +1,2 @@
The Thinking Game

213
prompt.md Normal file
View File

@@ -0,0 +1,213 @@
# SigHej — AI Session Prompt
> Paste this file at the start of a new AI coding session to get full project context.
---
## Hvad er SigHej?
SigHej er et **nudge-værktøj** til fysiske samtaler i den virkelige verden.
Idéen er simpel: folk i samme rum kan have interesser til fælles uden at vide det.
SigHej bruger BLE (Bluetooth Low Energy) til at opdage nærliggende brugere, tjekker om der er fælles interesser, og sender et diskret nudge til begge parter — *"nogen i nærheden deler din interesse for X — sig hej"*. Det er det. Ingen chat, ingen profiler, ingen social graph. Kun et lille skub til en rigtig samtale.
**Kerneprincipper:**
- Ingen beskedudveksling — nudget er produktet, ikke en chat-app
- Privacyby design — ingen lokationsdata, ingen bruger-ID'er, ephemerelle tokens
- Simpelt og fokuseret — ét skærmbillede, én knap, ét formål
---
## Monorepo-struktur
```
SigHej/
├── README.md
├── prompt.md ← dette fil
├── Makefile ← build workflow
├── docker-compose.yml ← backend + PostgreSQL + Redis
├── Docs/
│ ├── ARCHITECTURE.md ← system overview og data flow
│ ├── BACKEND.md ← FastAPI detaljer
│ ├── APP.md ← Flutter app detaljer
│ ├── ADMIN.md ← Next.js admin detaljer
│ ├── TESTING.md ← test- og kodestandarder
│ ├── DECISIONS.md ← ADR'er (architecture decision records)
│ └── colors.md ← "Sunny Beach Day" farvepalet
├── backend/ ← FastAPI Python
├── app/ ← Flutter Dart (Android + iOS + web)
└── admin/ ← Next.js TypeScript admin dashboard
```
---
## Teknisk arkitektur
### Flutter App (`app/`)
- **Sprog:** Dart, Flutter 3.x
- **BLE:** `flutter_blue_plus` ^1.36.8 — *kun scanning* på Android/iOS; web/macOS kører i demo-mode
- **State:** `provider` + `SessionStore` (ChangeNotifier)
- **Persistens:** `shared_preferences` — navn, tagline, interesser, bleToken
- **Screens:**
- `ProfileScreen` — første-gangs onboarding + redigering af profil
- `HomeScreen` — hoved-skærm med toggle-knap, pulseanimation og nudge-kort
- **Theme:** `app/lib/theme.dart` — "Sunny Beach Day" farvepalet, light + dark mode
- **Platform-betinget BLE:** `_bleAvailable` guard — alle BLE-kald er beskyttet; web/macOS simulerer match efter 3 sekunder (demo mode)
- **Web:** kører i Chrome via `flutter run -d chrome --web-port 8080`
**Farvepalet (Sunny Beach Day):**
| Rolle | Navn | Hex |
|---|---|---|
| Primary | Verdigris | `#2A9D8F` |
| Secondary | Sandy Brown | `#F4A261` |
| Tertiary | Jasmine | `#E9C46A` |
| Error/CTA | Burnt Peach | `#E76F51` |
| Surface dark | Charcoal Blue | `#264653` |
### Backend (`backend/`)
- **Sprog:** Python 3.12, FastAPI, async
- **Session store:** Redis (TTL: 2 timer — auto-sletning, ingen cleanup nødvendig)
- **Database:** PostgreSQL — kun anonymiserede aggregat-statistikker (ingen PII)
- **Kommunikation:** REST (POST /session, POST /match) + WebSocket (nudge push)
- **Config:** `pydantic-settings` — miljøvariabler, ingen hardcodede secrets
- **Tests:** pytest, httpx TestClient
### Admin Dashboard (`admin/`)
- **Sprog:** TypeScript, Next.js 14
- **Formål:** anonymiserede stats — pings, sessioner, interest counts
- **Ingen PII, ingen bruger-niveau data**
- **Build:** multi-stage Docker, `output: "standalone"`, kræver `package-lock.json`
---
## Data flow — happy path
```
Bruger A åbner app → vælger interesser → aktiverer "Åben for snak"
└─► App genererer ephemeral BLE token (UUID)
└─► App advertiser token via BLE (Android/iOS)
└─► App registrerer token + interesser: POST /session
Bruger B i nærheden scanner BLE → finder A's token
└─► App sender token: POST /match
└─► Backend: overlapper B's interesser med A's?
└─► Ja → WebSocket nudge til begge:
"Nogen i nærheden deler din interesse for X — sig hej"
└─► Brugerne lægger telefonen fra sig og siger hej
└─► Session udløber automatisk (Redis TTL: 2 timer)
```
---
## Makefile workflow
```bash
make up # start backend + PostgreSQL + Redis (Docker)
make down # stop services
make build # byg Docker images
make logs # følg logs
make be-dev # kør FastAPI i dev mode (hot reload)
make be-test # kør pytest
make be-lint # ruff lint
make be-fmt # ruff format
make adm-dev # kør Next.js admin i dev mode
make adm-build # byg admin til produktion
make app-get # flutter pub get
make app-analyze # flutter analyze (skal være "No issues found!")
make app-test # flutter test
make check # fuld CI: lint + test på alle tre komponenter
```
---
## Kodestandarder
### Python backend
Analyse kører via **DevOpsMCP** (`https://devops-mcp.i80.dk`):
```python
# Upload og analysér
session = DevOpsMCP-start_project_session("sighej-backend", [
{"file_path": "main.py", "content": "<indhold>"}
])
DevOpsMCP-check_personal_standards(file_path="main.py") # ingen error-findings
DevOpsMCP-analyze_complexity(file_path="main.py") # max complexity: 8
DevOpsMCP-add_type_hints(file_path="main.py") # min 80% coverage
DevOpsMCP-check_pep_compliance(file_path="main.py") # zero violations
```
**Regler:**
- Ingen `subprocess` med `shell=True` — brug SDK-klienter
- Ingen bare `except:` — fang specifikke exceptions
- Kun f-strings — ingen `%` eller `.format()`
- Ingen mutable default-argumenter
- Ingen hardcodede secrets — kun env vars via `pydantic-settings`
### Flutter app
- `flutter analyze` skal altid returnere `No issues found!`
- Alle BLE-kald skal guards af `_bleAvailable`
- Ingen direkte import af `dart:io` på web-paths
### Git commits
Brug conventional commits:
```
feat(app): tilføj pulsanimation til toggle-knap
fix(backend): ret Redis TTL ved session-fornyelse
docs: opdater ARCHITECTURE.md med WebSocket flow
```
---
## Arkitekturbeslutninger (ADR-resumé)
| ADR | Beslutning | Begrundelse |
|---|---|---|
| ADR-001 | Flutter frem for React Native | Native ARM, stabil BLE via direkte platform channels |
| ADR-002 | FastAPI frem for Express/Django | Async-first, Pydantic validation, hurtig at bygge |
| ADR-003 | Redis til sessions, ikke PostgreSQL | Ephemerelle data, auto-TTL, ingen manuel cleanup |
| ADR-004 | Ingen in-app messaging | Nudget ER produktet — chat modarbejder formålet |
---
## Privacy-design
- **Ingen bruger-ID'er** — kun ephemerelle BLE tokens (UUID, ny pr. session)
- **Ingen lokationsdata** — BLE proximity er ikke GPS
- **Server-side matching** — apps ser aldrig hinandens rå profiler
- **Auto-sletning** — Redis TTL 2 timer, ingen data efterlades
- **Aggregat-stats kun** — PostgreSQL indeholder aldrig PII
---
## Kendte begrænsninger (MVP)
- BLE advertising kræver fysisk Android/iOS-enhed — ikke tilgængeligt på macOS/web Flutter
- Web/macOS kører i **demo mode**: simulerer et match efter 3 sekunder
- Admin dashboard har ingen auth i MVP — bør tilføjes før produktion
- Ingen push notifications (FCM/APNs) endnu — nudge vises kun mens app er åben
- Android SDK + Xcode krævet for at bygge til mobile (se `flutter doctor`)
---
## Kom i gang (ny maskine)
```bash
# Klon repo
git clone <repo-url> && cd SigHej
# Start backend infrastruktur
make up
# Flutter app i browser
cd app && flutter pub get && flutter run -d chrome --web-port 8080
# Flutter app på Android (kræver USB debugging + Android SDK)
flutter devices
flutter run -d <device-id>
```
Se `Docs/` for detaljeret dokumentation om hvert komponent.