Compare commits
No commits in common. "dev" and "visual-voicemail" have entirely different histories.
dev
...
visual-voi
@ -1,38 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- website/**
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: debian
|
||||
defaults:
|
||||
run:
|
||||
working-directory: website
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: setup env
|
||||
run: |
|
||||
. ./.env || true
|
||||
if [ "${{ gitea.ref_name }}" == prod ] && [ -n "$PROD_URL" ]; then
|
||||
BASE_URL="$PROD_URL"
|
||||
else
|
||||
BASE_URL="${{ gitea.ref_name }}.$(tr / '\n' <<< "${{ gitea.repository }}" | tac | tr '\n' .)k8s.gmoker.com"
|
||||
fi
|
||||
REGISTRY="$(sed 's .*:// ' <<< ${{ gitea.server_url }})"
|
||||
cat <<EOF >> .env
|
||||
BASE_URL="$(printf '%s' "$BASE_URL" | tr '[:upper:]' '[:lower:]' | tr -c '[:lower:][:digit:]-.' -)"
|
||||
IMAGEAPP="$REGISTRY/$(printf '%s' "${{ gitea.repository }}:${{ gitea.ref_name }}" | tr '[:upper:]' '[:lower:]' | tr -c '[:lower:][:digit:]-/:_' _)"
|
||||
EOF
|
||||
cat .env
|
||||
|
||||
- uses: actions/kaniko@v1
|
||||
with:
|
||||
password: "${{ secrets.PKGRW }}"
|
||||
dockerfile: website/Dockerfile
|
||||
|
||||
- uses: actions/k8sdeploy@v1
|
||||
with:
|
||||
kubeconfig: "${{ secrets.K8S }}"
|
||||
registry_password: "${{ secrets.PKGRW }}"
|
||||
workdir: website
|
13
README.md
@ -1,13 +0,0 @@
|
||||
# Icing
|
||||
|
||||
An Epitech Innovation Project
|
||||
|
||||
*By*
|
||||
**Bartosz Michalak - Alexis Danlos - Florian Griffon - Ange Duhayon - Stéphane Corbière**
|
||||
|
||||
---
|
||||
|
||||
The **docs** folder contains documentation about:
|
||||
- The project
|
||||
- A user manual
|
||||
- Our automations
|
5
dialer/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# ICING Dialer
|
||||
|
||||
Project aiming to <i>ice</i> your **non-internet** phone calls, thanks to a custom and super-cryptographic dialer app.
|
||||
|
||||
Like & Follow !
|
1
dialer/android/.gitignore
vendored
@ -5,7 +5,6 @@ gradle-wrapper.jar
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
gradle.properties
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
|
@ -1,10 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS"/>
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||
<uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
|
||||
<uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
|
@ -1,10 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS"/>
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||
<uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
|
||||
<uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
|
||||
<application
|
||||
android:label="com.example.dialer"
|
||||
android:name="${applicationName}"
|
||||
|
@ -1,11 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS"/>
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
|
||||
<uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
|
@ -2,3 +2,5 @@ org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryErro
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
dev.steenbakker.mobile_scanner.useUnbundled=true
|
||||
org.gradle.java.home=/usr/lib/jvm/java-17-openjdk-amd64
|
||||
#org.gradle.java.home=/usr/lib/jvm/java-17-openjdk-17.0.13.0.11-3.fc41.x86_64
|
||||
|
34
dialer/ios/.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
**/dgph
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/ephemeral/
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
26
dialer/ios/Flutter/AppFrameworkInfo.plist
Normal file
@ -0,0 +1,26 @@
|
||||
<?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>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
1
dialer/ios/Flutter/Debug.xcconfig
Normal file
@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
1
dialer/ios/Flutter/Release.xcconfig
Normal file
@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
616
dialer/ios/Runner.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,616 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||
);
|
||||
name = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146EF1CF9000F007C117D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = RunnerTests;
|
||||
productName = RunnerTests;
|
||||
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C8080294A63A400263BE5 = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||
};
|
||||
97C146ED1CF9000F007C117D = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 1100;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
331C807F294A63A400263BE5 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
331C807D294A63A400263BE5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
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_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
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_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.dialer;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
331C8088294A63A400263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.dialer.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
331C8089294A63A400263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.dialer.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
331C808A294A63A400263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.dialer.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
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_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
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_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
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_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147041CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
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_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
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_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
97C147061CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.dialer;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.dialer;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
331C8088294A63A400263BE5 /* Debug */,
|
||||
331C8089294A63A400263BE5 /* Release */,
|
||||
331C808A294A63A400263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
7
dialer/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
@ -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>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,98 @@
|
||||
<?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 = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.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 = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||
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"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.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 = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
@ -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>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
13
dialer/ios/Runner/AppDelegate.swift
Normal file
@ -0,0 +1,13 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 295 B |
After Width: | Height: | Size: 406 B |
After Width: | Height: | Size: 450 B |
After Width: | Height: | Size: 282 B |
After Width: | Height: | Size: 462 B |
After Width: | Height: | Size: 704 B |
After Width: | Height: | Size: 406 B |
After Width: | Height: | Size: 586 B |
After Width: | Height: | Size: 862 B |
After Width: | Height: | Size: 862 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 762 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
23
dialer/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
dialer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
After Width: | Height: | Size: 68 B |
BIN
dialer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
After Width: | Height: | Size: 68 B |
BIN
dialer/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
After Width: | Height: | Size: 68 B |
5
dialer/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
37
dialer/ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
26
dialer/ios/Runner/Base.lproj/Main.storyboard
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
52
dialer/ios/Runner/Info.plist
Normal file
@ -0,0 +1,52 @@
|
||||
<?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>CFBundleDisplayName</key>
|
||||
<string>Dialer</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>dialer</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>Contacts for calls/string>
|
||||
|
||||
</dict>
|
||||
</plist>
|
1
dialer/ios/Runner/Runner-Bridging-Header.h
Normal file
@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
12
dialer/ios/RunnerTests/RunnerTests.swift
Normal file
@ -0,0 +1,12 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
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.
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../widgets/contact_service.dart';
|
||||
import '../contacts/widgets/add_contact_button.dart';
|
||||
|
||||
class CompositionPage extends StatefulWidget {
|
||||
const CompositionPage({super.key});
|
||||
@ -15,7 +12,6 @@ class _CompositionPageState extends State<CompositionPage> {
|
||||
String dialedNumber = "";
|
||||
List<Contact> _allContacts = [];
|
||||
List<Contact> _filteredContacts = [];
|
||||
final ContactService _contactService = ContactService();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -24,9 +20,11 @@ class _CompositionPageState extends State<CompositionPage> {
|
||||
}
|
||||
|
||||
Future<void> _fetchContacts() async {
|
||||
_allContacts = await _contactService.fetchContacts();
|
||||
_filteredContacts = _allContacts;
|
||||
setState(() {});
|
||||
if (await FlutterContacts.requestPermission()) {
|
||||
_allContacts = await FlutterContacts.getContacts(withProperties: true);
|
||||
_filteredContacts = _allContacts;
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
void _filterContacts() {
|
||||
@ -65,24 +63,9 @@ class _CompositionPageState extends State<CompositionPage> {
|
||||
});
|
||||
}
|
||||
|
||||
// Function to call a contact's number
|
||||
void _launchPhoneDialer(String phoneNumber) async {
|
||||
final uri = Uri(scheme: 'tel', path: phoneNumber);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri);
|
||||
} else {
|
||||
debugPrint('Could not launch $phoneNumber');
|
||||
}
|
||||
}
|
||||
|
||||
// Function to send an SMS to a contact's number
|
||||
void _launchSms(String phoneNumber) async {
|
||||
final uri = Uri(scheme: 'sms', path: phoneNumber);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri);
|
||||
} else {
|
||||
debugPrint('Could not send SMS to $phoneNumber');
|
||||
}
|
||||
// Placeholder function for adding contact
|
||||
void addContact(String number) {
|
||||
// This function is empty for now
|
||||
}
|
||||
|
||||
@override
|
||||
@ -96,9 +79,9 @@ class _CompositionPageState extends State<CompositionPage> {
|
||||
// Top half: Display contacts matching dialed number
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 42.0, left: 16.0, right: 16.0, bottom: 16.0),
|
||||
child:
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 42.0, left: 16.0, right: 16.0, bottom: 16.0),
|
||||
color: Colors.black,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -107,39 +90,32 @@ class _CompositionPageState extends State<CompositionPage> {
|
||||
child: ListView(
|
||||
children: _filteredContacts.isNotEmpty
|
||||
? _filteredContacts.map((contact) {
|
||||
final phoneNumber = contact.phones.isNotEmpty
|
||||
? contact.phones.first.number
|
||||
: 'No phone number';
|
||||
return ListTile(
|
||||
title: Text(
|
||||
contact.displayName,
|
||||
style:
|
||||
const TextStyle(color: Colors.white),
|
||||
),
|
||||
subtitle: Text(
|
||||
phoneNumber,
|
||||
style:
|
||||
const TextStyle(color: Colors.grey),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
subtitle: contact.phones.isNotEmpty
|
||||
? Text(
|
||||
contact.phones.first.number,
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
)
|
||||
: null,
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Call button
|
||||
IconButton(
|
||||
icon: Icon(Icons.phone,
|
||||
color: Colors.green[300],
|
||||
size: 20),
|
||||
icon: Icon(Icons.phone, color: Colors.green[300], size: 20),
|
||||
onPressed: () {
|
||||
_launchPhoneDialer(phoneNumber);
|
||||
print('Calling ${contact.displayName}');
|
||||
},
|
||||
),
|
||||
// Message button
|
||||
// Text button
|
||||
IconButton(
|
||||
icon: Icon(Icons.message,
|
||||
color: Colors.blue[300],
|
||||
size: 20),
|
||||
icon: Icon(Icons.message, color: Colors.blue[300], size: 20),
|
||||
onPressed: () {
|
||||
_launchSms(phoneNumber);
|
||||
print('Texting ${contact.displayName}');
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -149,12 +125,7 @@ class _CompositionPageState extends State<CompositionPage> {
|
||||
},
|
||||
);
|
||||
}).toList()
|
||||
: [
|
||||
Center(
|
||||
child: Text('No contacts found',
|
||||
style:
|
||||
TextStyle(color: Colors.white)))
|
||||
],
|
||||
: [Center(child: Text('No contacts found', style: TextStyle(color: Colors.white)))],
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -179,16 +150,14 @@ class _CompositionPageState extends State<CompositionPage> {
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
dialedNumber,
|
||||
style: const TextStyle(
|
||||
fontSize: 24, color: Colors.white),
|
||||
style: const TextStyle(fontSize: 24, color: Colors.white),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _onClearPress,
|
||||
icon: const Icon(Icons.backspace,
|
||||
color: Colors.white),
|
||||
icon: const Icon(Icons.backspace, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -201,8 +170,7 @@ class _CompositionPageState extends State<CompositionPage> {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceEvenly,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildDialButton('1'),
|
||||
_buildDialButton('2'),
|
||||
@ -210,8 +178,7 @@ class _CompositionPageState extends State<CompositionPage> {
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceEvenly,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildDialButton('4'),
|
||||
_buildDialButton('5'),
|
||||
@ -219,8 +186,7 @@ class _CompositionPageState extends State<CompositionPage> {
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceEvenly,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildDialButton('7'),
|
||||
_buildDialButton('8'),
|
||||
@ -228,8 +194,7 @@ class _CompositionPageState extends State<CompositionPage> {
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceEvenly,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildDialButton('*'),
|
||||
_buildDialButton('0'),
|
||||
@ -244,19 +209,20 @@ class _CompositionPageState extends State<CompositionPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Add Contact Button with empty function call
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20.0),
|
||||
child: FloatingActionButton(
|
||||
backgroundColor: Colors.blue,
|
||||
onPressed: () {
|
||||
addContact(dialedNumber);
|
||||
},
|
||||
child: const Icon(Icons.person_add, color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Add Contact Button
|
||||
Positioned(
|
||||
bottom: 20.0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: AddContactButton(),
|
||||
),
|
||||
),
|
||||
|
||||
// Top Row with Back Arrow
|
||||
Positioned(
|
||||
top: 40.0,
|
||||
|
@ -17,10 +17,8 @@ class _ContactPageState extends State<ContactPage> {
|
||||
return Scaffold(
|
||||
body: contactState.loading
|
||||
? const LoadingIndicatorWidget()
|
||||
: AlphabetScrollPage(
|
||||
scrollOffset: contactState.scrollOffset,
|
||||
contacts: contactState.contacts, // Use all contacts here
|
||||
),
|
||||
// : ContactListWidget(contacts: contactState.contacts),
|
||||
: AlphabetScrollPage(contacts: contactState.contacts, scrollOffset: contactState.scrollOffset),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
15
dialer/lib/features/contacts/contact_service.dart
Normal file
@ -0,0 +1,15 @@
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
|
||||
// Service to manage contact-related operations
|
||||
class ContactService {
|
||||
Future<List<Contact>> fetchContacts() async {
|
||||
if (await FlutterContacts.requestPermission()) {
|
||||
return await FlutterContacts.getContacts(withProperties: true, withThumbnail: true, withAccounts: true, withGroups: true);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
Future<void> addNewContact(Contact contact) async {
|
||||
await FlutterContacts.insertContact(contact);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
import '../../widgets/contact_service.dart';
|
||||
import 'contact_service.dart';
|
||||
|
||||
class ContactState extends StatefulWidget {
|
||||
final Widget child;
|
||||
@ -8,9 +8,7 @@ class ContactState extends StatefulWidget {
|
||||
const ContactState({super.key, required this.child});
|
||||
|
||||
static _ContactStateState of(BuildContext context) {
|
||||
return context
|
||||
.dependOnInheritedWidgetOfExactType<_InheritedContactState>()!
|
||||
.data;
|
||||
return context.dependOnInheritedWidgetOfExactType<_InheritedContactState>()!.data;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -19,15 +17,12 @@ class ContactState extends StatefulWidget {
|
||||
|
||||
class _ContactStateState extends State<ContactState> {
|
||||
final ContactService _contactService = ContactService();
|
||||
List<Contact> _allContacts = [];
|
||||
List<Contact> _favoriteContacts = [];
|
||||
List<Contact> _contacts = [];
|
||||
bool _loading = true;
|
||||
double _scrollOffset = 0.0;
|
||||
Contact? _selfContact = Contact();
|
||||
|
||||
// Getters for all contacts and favorites
|
||||
List<Contact> get contacts => _allContacts;
|
||||
List<Contact> get favoriteContacts => _favoriteContacts;
|
||||
List<Contact> get contacts => _contacts;
|
||||
bool get loading => _loading;
|
||||
double get scrollOffset => _scrollOffset;
|
||||
Contact? get selfContact => _selfContact;
|
||||
@ -35,46 +30,31 @@ class _ContactStateState extends State<ContactState> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
fetchContacts(); // Fetch all contacts by default
|
||||
_fetchContacts();
|
||||
|
||||
// Add listener for contact changes
|
||||
FlutterContacts.addListener(_onContactChange);
|
||||
}
|
||||
|
||||
void _onContactChange() => fetchContacts();
|
||||
void _onContactChange() => _fetchContacts();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Remove listener
|
||||
FlutterContacts.removeListener(_onContactChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// Fetch all contacts
|
||||
Future<void> fetchContacts() async {
|
||||
setState(() => _loading = true);
|
||||
try {
|
||||
List<Contact> contacts = await _contactService.fetchContacts();
|
||||
_processContacts(contacts);
|
||||
} finally {
|
||||
setState(() => _loading = false);
|
||||
}
|
||||
}
|
||||
Future<void> _fetchContacts() async {
|
||||
List<Contact> contacts = await _contactService.fetchContacts();
|
||||
|
||||
// Fetch only favorite contacts
|
||||
Future<void> fetchFavoriteContacts() async {
|
||||
setState(() => _loading = true);
|
||||
try {
|
||||
List<Contact> contacts = await _contactService.fetchFavoriteContacts();
|
||||
setState(() => _favoriteContacts = contacts);
|
||||
} finally {
|
||||
setState(() => _loading = false);
|
||||
}
|
||||
}
|
||||
debugPrint("Fetched ${contacts.length} contacts");
|
||||
|
||||
void _processContacts(List<Contact> contacts) {
|
||||
// Find selfContact before filtering
|
||||
_selfContact = contacts.firstWhere(
|
||||
(contact) => contact.displayName.toLowerCase() == "user",
|
||||
orElse: () => Contact(),
|
||||
);
|
||||
|
||||
if (_selfContact!.phones.isEmpty) {
|
||||
debugPrint("Self contact has no phone numbers");
|
||||
_selfContact = null;
|
||||
@ -82,18 +62,16 @@ class _ContactStateState extends State<ContactState> {
|
||||
|
||||
contacts = contacts.where((contact) => contact.phones.isNotEmpty).toList();
|
||||
contacts.sort((a, b) => a.displayName.compareTo(b.displayName));
|
||||
|
||||
setState(() {
|
||||
_allContacts = contacts;
|
||||
_favoriteContacts =
|
||||
contacts.where((contact) => contact.isStarred).toList();
|
||||
_contacts = contacts;
|
||||
_loading = false;
|
||||
_selfContact = _selfContact;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> addNewContact(Contact contact) async {
|
||||
await _contactService.addNewContact(contact);
|
||||
await fetchContacts();
|
||||
await _fetchContacts();
|
||||
}
|
||||
|
||||
void setScrollOffset(double offset) {
|
||||
@ -111,7 +89,6 @@ class _ContactStateState extends State<ContactState> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class _InheritedContactState extends InheritedWidget {
|
||||
final _ContactStateState data;
|
||||
|
||||
|
@ -1,21 +1,16 @@
|
||||
import 'package:dialer/widgets/username_color_generator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
import '../contact_state.dart';
|
||||
import '../../../widgets/color_darkener.dart';
|
||||
import '../contact_state.dart';
|
||||
import 'add_contact_button.dart';
|
||||
import 'contact_modal.dart';
|
||||
import 'share_own_qr.dart';
|
||||
|
||||
class AlphabetScrollPage extends StatefulWidget {
|
||||
final double scrollOffset;
|
||||
final List<Contact> contacts;
|
||||
final double scrollOffset;
|
||||
|
||||
const AlphabetScrollPage({
|
||||
super.key,
|
||||
required this.scrollOffset,
|
||||
required this.contacts,
|
||||
});
|
||||
const AlphabetScrollPage({super.key, required this.contacts, required this.scrollOffset});
|
||||
|
||||
@override
|
||||
_AlphabetScrollPageState createState() => _AlphabetScrollPageState();
|
||||
@ -36,53 +31,11 @@ class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
|
||||
contactState.setScrollOffset(_scrollController.offset);
|
||||
}
|
||||
|
||||
Future<void> _refreshContacts() async {
|
||||
final contactState = ContactState.of(context);
|
||||
try {
|
||||
await contactState.fetchContacts();
|
||||
} catch (e) {
|
||||
print('Error refreshing contacts: $e');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Failed to refresh contacts')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _toggleFavorite(Contact contact) async {
|
||||
try {
|
||||
if (await FlutterContacts.requestPermission()) {
|
||||
Contact? fullContact = await FlutterContacts.getContact(contact.id,
|
||||
withProperties: true,
|
||||
withAccounts: true,
|
||||
withPhoto: true,
|
||||
withThumbnail: true);
|
||||
|
||||
if (fullContact != null) {
|
||||
fullContact.isStarred = !fullContact.isStarred;
|
||||
await FlutterContacts.updateContact(fullContact);
|
||||
}
|
||||
await _refreshContacts();
|
||||
} else {
|
||||
print("Could not fetch contact details");
|
||||
}
|
||||
} catch (e) {
|
||||
print("Error updating favorite status: $e");
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Failed to update contact favorite status')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final contacts = widget.contacts;
|
||||
final selfContact = ContactState.of(context).selfContact;
|
||||
|
||||
Map<String, List<Contact>> alphabetizedContacts = {};
|
||||
for (var contact in contacts) {
|
||||
String firstLetter = contact.displayName.isNotEmpty
|
||||
? contact.displayName[0].toUpperCase()
|
||||
: '#';
|
||||
for (var contact in widget.contacts) {
|
||||
String firstLetter = contact.displayName.isNotEmpty ? contact.displayName[0].toUpperCase() : '#';
|
||||
if (!alphabetizedContacts.containsKey(firstLetter)) {
|
||||
alphabetizedContacts[firstLetter] = [];
|
||||
}
|
||||
@ -94,8 +47,7 @@ class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Column(
|
||||
children: [
|
||||
// Top buttons row
|
||||
children: [ // Top buttons row
|
||||
Container(
|
||||
color: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
@ -103,7 +55,7 @@ class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
AddContactButton(),
|
||||
QRCodeButton(contacts: contacts, selfContact: selfContact),
|
||||
QRCodeButton(contacts: widget.contacts, selfContact: ContactState.of(context).selfContact),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -114,14 +66,13 @@ class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
|
||||
itemCount: alphabetKeys.length,
|
||||
itemBuilder: (context, index) {
|
||||
String letter = alphabetKeys[index];
|
||||
List<Contact> contactsForLetter = alphabetizedContacts[letter]!;
|
||||
List<Contact> contacts = alphabetizedContacts[letter]!;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Alphabet Letter Header
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0, horizontal: 16.0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
|
||||
child: Text(
|
||||
letter,
|
||||
style: TextStyle(
|
||||
@ -132,74 +83,25 @@ class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
|
||||
),
|
||||
),
|
||||
// Contact Entries
|
||||
...contactsForLetter.map((contact) {
|
||||
String phoneNumber = contact.phones.isNotEmpty
|
||||
? contact.phones.first.number
|
||||
: 'No phone number';
|
||||
Color avatarColor =
|
||||
generateColorFromName(contact.displayName);
|
||||
...contacts.map((contact) {
|
||||
String phoneNumber = contact.phones.isNotEmpty ? contact.phones.first.number : 'No phone number';
|
||||
Color avatarColor = generateColorFromName(contact.displayName);
|
||||
return ListTile(
|
||||
leading: (contact.thumbnail != null &&
|
||||
contact.thumbnail!.isNotEmpty)
|
||||
leading: (contact.thumbnail != null && contact.thumbnail!.isNotEmpty)
|
||||
? CircleAvatar(
|
||||
backgroundImage:
|
||||
MemoryImage(contact.thumbnail!),
|
||||
)
|
||||
backgroundImage: MemoryImage(contact.thumbnail!),
|
||||
)
|
||||
: CircleAvatar(
|
||||
backgroundColor: avatarColor,
|
||||
child: Text(
|
||||
contact.displayName.isNotEmpty
|
||||
? contact.displayName[0].toUpperCase()
|
||||
: '?',
|
||||
style: TextStyle(
|
||||
color: darken(avatarColor, 0.4)),
|
||||
),
|
||||
),
|
||||
title: Text(contact.displayName,
|
||||
style: TextStyle(color: Colors.white)),
|
||||
subtitle: Text(phoneNumber,
|
||||
style: TextStyle(color: Colors.white70)),
|
||||
backgroundColor: avatarColor,
|
||||
child: Text(
|
||||
contact.displayName.isNotEmpty ? contact.displayName[0].toUpperCase() : '?',
|
||||
style: TextStyle(color: darken(avatarColor, 0.4)),
|
||||
),
|
||||
),
|
||||
title: Text(contact.displayName, style: TextStyle(color: Colors.white)),
|
||||
subtitle: Text(phoneNumber, style: TextStyle(color: Colors.white70)),
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) {
|
||||
return ContactModal(
|
||||
contact: contact,
|
||||
onEdit: () async {
|
||||
if (await FlutterContacts.requestPermission()) {
|
||||
final updatedContact =
|
||||
await FlutterContacts.openExternalEdit(
|
||||
contact.id);
|
||||
if (updatedContact != null) {
|
||||
await _refreshContacts();
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'${contact.displayName} updated successfully!'),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(
|
||||
content:
|
||||
Text('Edit canceled or failed.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
onToggleFavorite: () {
|
||||
_toggleFavorite(contact);
|
||||
},
|
||||
isFavorite: contact.isStarred,
|
||||
);
|
||||
},
|
||||
);
|
||||
// Handle contact tap
|
||||
},
|
||||
);
|
||||
}),
|
||||
|
@ -1,341 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:dialer/widgets/username_color_generator.dart';
|
||||
import '../../../widgets/block_service.dart';
|
||||
|
||||
class ContactModal extends StatefulWidget {
|
||||
final Contact contact;
|
||||
final Function onEdit;
|
||||
final Function onToggleFavorite;
|
||||
final bool isFavorite;
|
||||
|
||||
const ContactModal({
|
||||
super.key,
|
||||
required this.contact,
|
||||
required this.onEdit,
|
||||
required this.onToggleFavorite,
|
||||
required this.isFavorite,
|
||||
});
|
||||
|
||||
@override
|
||||
_ContactModalState createState() => _ContactModalState();
|
||||
}
|
||||
|
||||
class _ContactModalState extends State<ContactModal> {
|
||||
late String phoneNumber;
|
||||
bool isBlocked = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
phoneNumber = widget.contact.phones.isNotEmpty
|
||||
? widget.contact.phones.first.number
|
||||
: 'No phone number';
|
||||
_checkIfBlocked();
|
||||
}
|
||||
|
||||
Future<void> _checkIfBlocked() async {
|
||||
if (phoneNumber != 'No phone number') {
|
||||
bool blocked = await BlockService().isNumberBlocked(phoneNumber);
|
||||
setState(() {
|
||||
isBlocked = blocked;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _toggleBlockState() async {
|
||||
if (phoneNumber == 'No phone number') {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('No phone number to block or unblock')),
|
||||
);
|
||||
} else if (isBlocked) {
|
||||
await BlockService().unblockNumber(phoneNumber);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('$phoneNumber has been unblocked')),
|
||||
);
|
||||
} else {
|
||||
await BlockService().blockNumber(phoneNumber);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('$phoneNumber has been blocked')),
|
||||
);
|
||||
}
|
||||
|
||||
if (phoneNumber != 'No phone number') {
|
||||
_checkIfBlocked();
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
void _launchPhoneDialer(String phoneNumber) async {
|
||||
final uri = Uri(scheme: 'tel', path: phoneNumber);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri);
|
||||
} else {
|
||||
debugPrint('Could not launch $phoneNumber');
|
||||
}
|
||||
}
|
||||
|
||||
void _launchSms(String phoneNumber) async {
|
||||
final uri = Uri(scheme: 'sms', path: phoneNumber);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri);
|
||||
} else {
|
||||
debugPrint('Could not launch SMS to $phoneNumber');
|
||||
}
|
||||
}
|
||||
|
||||
void _launchEmail(String email) async {
|
||||
final uri = Uri(scheme: 'mailto', path: email);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri);
|
||||
} else {
|
||||
debugPrint('Could not launch email to $email');
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteContact() async {
|
||||
final bool shouldDelete = await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Delete Contact'),
|
||||
content: Text('Are you sure you want to delete ${widget.contact.displayName}?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: const Text('Delete'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (shouldDelete) {
|
||||
try {
|
||||
// Delete the contact
|
||||
await FlutterContacts.deleteContact(widget.contact);
|
||||
|
||||
// Show success message
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('${widget.contact.displayName} deleted')),
|
||||
);
|
||||
|
||||
// Close the modal
|
||||
Navigator.of(context).pop();
|
||||
} catch (e) {
|
||||
// Handle errors and show a failure message
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Failed to delete ${widget.contact.displayName}: $e')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String email = widget.contact.emails.isNotEmpty
|
||||
? widget.contact.emails.first.address
|
||||
: 'No email';
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
child: GestureDetector(
|
||||
onTap: () {},
|
||||
child: FractionallySizedBox(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[900],
|
||||
borderRadius:
|
||||
const BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Modal Handle and Three-Dot Menu
|
||||
Stack(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade300,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 10, right: 10),
|
||||
child: PopupMenuButton<String>(
|
||||
icon: const Icon(Icons.more_vert,
|
||||
color: Colors.white),
|
||||
onSelected: (String choice) {
|
||||
if (choice == 'delete') {
|
||||
_deleteContact();
|
||||
}
|
||||
},
|
||||
itemBuilder: (BuildContext context) {
|
||||
return [
|
||||
const PopupMenuItem<String>(
|
||||
value: 'show_associated_contacts',
|
||||
child: Text('Show associated contacts'),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'delete',
|
||||
child: Text('Delete'),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'share',
|
||||
child: Text('Share (via QR code)'),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'create_shortcut',
|
||||
child:
|
||||
Text('Create shortcut (to home screen)'),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'set_ringtone',
|
||||
child: Text('Set ringtone'),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Contact Profile
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundImage: (widget.contact.thumbnail != null &&
|
||||
widget.contact.thumbnail!.isNotEmpty)
|
||||
? MemoryImage(widget.contact.thumbnail!)
|
||||
: null,
|
||||
backgroundColor:
|
||||
generateColorFromName(widget.contact.displayName),
|
||||
child: (widget.contact.thumbnail == null ||
|
||||
widget.contact.thumbnail!.isEmpty)
|
||||
? Text(
|
||||
widget.contact.displayName.isNotEmpty
|
||||
? widget.contact.displayName[0]
|
||||
.toUpperCase()
|
||||
: '?',
|
||||
style: const TextStyle(
|
||||
fontSize: 40, color: Colors.white),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
widget.contact.displayName,
|
||||
style: const TextStyle(
|
||||
fontSize: 24, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
// Contact Actions
|
||||
ListTile(
|
||||
leading: const Icon(Icons.phone, color: Colors.green),
|
||||
title: Text(phoneNumber),
|
||||
onTap: () {
|
||||
if (widget.contact.phones.isNotEmpty) {
|
||||
_launchPhoneDialer(phoneNumber);
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.message, color: Colors.blue),
|
||||
title: Text(phoneNumber),
|
||||
onTap: () {
|
||||
if (widget.contact.phones.isNotEmpty) {
|
||||
_launchSms(phoneNumber);
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.email, color: Colors.orange),
|
||||
title: Text(email),
|
||||
onTap: () {
|
||||
if (widget.contact.emails.isNotEmpty) {
|
||||
_launchEmail(email);
|
||||
}
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
// Favorite, Edit, and Block/Unblock Buttons
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// Favorite button
|
||||
SizedBox(
|
||||
width: double
|
||||
.infinity, // This makes the button take full width
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
widget.onToggleFavorite();
|
||||
},
|
||||
icon: Icon(widget.isFavorite
|
||||
? Icons.star
|
||||
: Icons.star_border),
|
||||
label: Text(
|
||||
widget.isFavorite ? 'Unfavorite' : 'Favorite'),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10), // Space between buttons
|
||||
|
||||
// Edit button
|
||||
SizedBox(
|
||||
width: double
|
||||
.infinity, // This makes the button take full width
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => widget.onEdit(),
|
||||
icon: const Icon(Icons.edit),
|
||||
label: const Text('Edit Contact'),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10), // Space between buttons
|
||||
|
||||
// Block/Unblock button
|
||||
SizedBox(
|
||||
width: double
|
||||
.infinity, // This makes the button take full width
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _toggleBlockState,
|
||||
icon: Icon(
|
||||
isBlocked ? Icons.block : Icons.block_flipped),
|
||||
label: Text(isBlocked ? 'Unblock' : 'Block'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,32 +1,23 @@
|
||||
import 'package:dialer/features/contacts/contact_state.dart';
|
||||
import 'package:dialer/features/contacts/widgets/alphabet_scroll_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:dialer/widgets/loading_indicator.dart';
|
||||
|
||||
class FavoritesPage extends StatefulWidget {
|
||||
const FavoritesPage({super.key});
|
||||
class FavoritePage extends StatefulWidget {
|
||||
const FavoritePage({super.key});
|
||||
|
||||
@override
|
||||
_FavoritesPageState createState() => _FavoritesPageState();
|
||||
_FavoritePageState createState() => _FavoritePageState();
|
||||
}
|
||||
|
||||
class _FavoritesPageState extends State<FavoritesPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
class _FavoritePageState extends State<FavoritePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final contactState = ContactState.of(context);
|
||||
return Scaffold(
|
||||
body: contactState.loading
|
||||
? const LoadingIndicatorWidget()
|
||||
: AlphabetScrollPage(
|
||||
scrollOffset: contactState.scrollOffset,
|
||||
contacts:
|
||||
contactState.favoriteContacts, // Use only favorites here
|
||||
),
|
||||
backgroundColor: Colors.black,
|
||||
body: Center( // Center the text within the body
|
||||
child: Text(
|
||||
"Hello",
|
||||
style: TextStyle(color: Colors.white), // Change text color for visibility
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
// history_page.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:intl/intl.dart'; // For date formatting
|
||||
import 'package:dialer/features/contacts/contact_state.dart';
|
||||
import 'package:dialer/widgets/username_color_generator.dart';
|
||||
import 'package:dialer/widgets/color_darkener.dart';
|
||||
|
||||
class History {
|
||||
final Contact contact;
|
||||
final DateTime date;
|
||||
final String callType; // 'incoming' or 'outgoing'
|
||||
final String callType; // 'incoming' or 'outgoing'
|
||||
final String callStatus; // 'missed' or 'answered'
|
||||
final int attempts;
|
||||
|
||||
@ -29,10 +28,9 @@ class HistoryPage extends StatefulWidget {
|
||||
_HistoryPageState createState() => _HistoryPageState();
|
||||
}
|
||||
|
||||
class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStateMixin {
|
||||
class _HistoryPageState extends State<HistoryPage> {
|
||||
List<History> histories = [];
|
||||
bool loading = true;
|
||||
int? _expandedIndex;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
@ -53,6 +51,7 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
|
||||
}
|
||||
List<Contact> contacts = contactState.contacts;
|
||||
|
||||
// Ensure there are enough contacts
|
||||
if (contacts.isEmpty) {
|
||||
setState(() {
|
||||
loading = false;
|
||||
@ -60,6 +59,7 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
|
||||
return;
|
||||
}
|
||||
|
||||
// Build histories using the contacts
|
||||
setState(() {
|
||||
histories = List.generate(
|
||||
contacts.length >= 10 ? 10 : contacts.length,
|
||||
@ -75,47 +75,6 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
|
||||
});
|
||||
}
|
||||
|
||||
List _buildGroupedList(List<History> historyList) {
|
||||
// Sort histories by date (most recent first)
|
||||
historyList.sort((a, b) => b.date.compareTo(a.date));
|
||||
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final yesterday = today.subtract(const Duration(days: 1));
|
||||
|
||||
List<History> todayHistories = [];
|
||||
List<History> yesterdayHistories = [];
|
||||
List<History> olderHistories = [];
|
||||
|
||||
for (var history in historyList) {
|
||||
final callDate = DateTime(history.date.year, history.date.month, history.date.day);
|
||||
if (callDate == today) {
|
||||
todayHistories.add(history);
|
||||
} else if (callDate == yesterday) {
|
||||
yesterdayHistories.add(history);
|
||||
} else {
|
||||
olderHistories.add(history);
|
||||
}
|
||||
}
|
||||
|
||||
// Combine them with headers
|
||||
final items = <dynamic>[];
|
||||
if (todayHistories.isNotEmpty) {
|
||||
items.add('Today');
|
||||
items.addAll(todayHistories);
|
||||
}
|
||||
if (yesterdayHistories.isNotEmpty) {
|
||||
items.add('Yesterday');
|
||||
items.addAll(yesterdayHistories);
|
||||
}
|
||||
if (olderHistories.isNotEmpty) {
|
||||
items.add('Older');
|
||||
items.addAll(olderHistories);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final contactState = ContactState.of(context);
|
||||
@ -123,6 +82,9 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
|
||||
if (loading || contactState.loading) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: AppBar(
|
||||
title: const Text('History'),
|
||||
),
|
||||
body: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
@ -132,6 +94,9 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
|
||||
if (histories.isEmpty) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: AppBar(
|
||||
title: const Text('History'),
|
||||
),
|
||||
body: const Center(
|
||||
child: Text(
|
||||
'No call history available.',
|
||||
@ -141,298 +106,46 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
|
||||
);
|
||||
}
|
||||
|
||||
// Filter missed calls
|
||||
List<History> missedCalls = histories.where((h) => h.callStatus == 'missed').toList();
|
||||
|
||||
final allItems = _buildGroupedList(histories);
|
||||
final missedItems = _buildGroupedList(missedCalls);
|
||||
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
||||
child: Container(
|
||||
color: Colors.black,
|
||||
child: const TabBar(
|
||||
tabs: [
|
||||
Tab(text: 'All Calls'),
|
||||
Tab(text: 'Missed Calls'),
|
||||
],
|
||||
indicatorColor: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: TabBarView(
|
||||
children: [
|
||||
// All Calls
|
||||
_buildListView(allItems),
|
||||
// Missed Calls
|
||||
_buildListView(missedItems),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildListView(List items) {
|
||||
return ListView.builder(
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
|
||||
if (item is String) {
|
||||
// This is a header item
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
color: Colors.grey[900],
|
||||
child: Text(
|
||||
item,
|
||||
style: const TextStyle(
|
||||
color: Colors.white70,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (item is History) {
|
||||
final history = item;
|
||||
final contact = history.contact;
|
||||
final isExpanded = _expandedIndex == index;
|
||||
|
||||
// Generate the avatar color
|
||||
Color avatarColor = generateColorFromName(contact.displayName);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: (contact.thumbnail != null && contact.thumbnail!.isNotEmpty)
|
||||
? CircleAvatar(
|
||||
backgroundImage: MemoryImage(contact.thumbnail!),
|
||||
)
|
||||
: CircleAvatar(
|
||||
backgroundColor: avatarColor,
|
||||
child: Text(
|
||||
contact.displayName.isNotEmpty
|
||||
? contact.displayName[0].toUpperCase()
|
||||
: '?',
|
||||
style: TextStyle(color: darken(avatarColor, 0.4)),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
contact.displayName,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
subtitle: Text(
|
||||
'${history.callType} - ${history.callStatus} - ${DateFormat('MMM dd, hh:mm a').format(history.date)}',
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'${history.attempts}x',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.phone, color: Colors.green),
|
||||
onPressed: () async {
|
||||
if (contact.phones.isNotEmpty) {
|
||||
final Uri callUri =
|
||||
Uri(scheme: 'tel', path: contact.phones.first.number);
|
||||
if (await canLaunchUrl(callUri)) {
|
||||
await launchUrl(callUri);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Could not launch call')),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Contact has no phone number')),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_expandedIndex = isExpanded ? null : index;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (isExpanded)
|
||||
Container(
|
||||
color: Colors.grey[850],
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () async {
|
||||
if (history.contact.phones.isNotEmpty) {
|
||||
final Uri smsUri =
|
||||
Uri(scheme: 'sms', path: history.contact.phones.first.number);
|
||||
if (await canLaunchUrl(smsUri)) {
|
||||
await launchUrl(smsUri);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Could not send message')),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Contact has no phone number')),
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.message, color: Colors.white),
|
||||
label: const Text('Message', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Navigate to Call Details page
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => CallDetailsPage(history: history),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.info, color: Colors.white),
|
||||
label: const Text('Details', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Implement block number functionality
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Number blocked (functionality not implemented)'),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.block, color: Colors.white),
|
||||
label: const Text('Block', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CallDetailsPage extends StatelessWidget {
|
||||
final History history;
|
||||
|
||||
const CallDetailsPage({Key? key, required this.history}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final contact = history.contact;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: AppBar(
|
||||
title: const Text('Call Details'),
|
||||
backgroundColor: Colors.black,
|
||||
title: const Text('History'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// Display Contact Name and Thumbnail
|
||||
Row(
|
||||
children: [
|
||||
(contact.thumbnail != null && contact.thumbnail!.isNotEmpty)
|
||||
? CircleAvatar(
|
||||
backgroundImage: MemoryImage(contact.thumbnail!),
|
||||
radius: 30,
|
||||
)
|
||||
: CircleAvatar(
|
||||
backgroundColor: Colors.grey[700],
|
||||
radius: 30,
|
||||
child: Text(
|
||||
contact.displayName.isNotEmpty
|
||||
? contact.displayName[0].toUpperCase()
|
||||
: '?',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
contact.displayName,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 24),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
body: ListView.builder(
|
||||
itemCount: histories.length,
|
||||
itemBuilder: (context, index) {
|
||||
final history = histories[index];
|
||||
final contact = history.contact;
|
||||
|
||||
// Display call type, status, date, attempts
|
||||
DetailRow(
|
||||
label: 'Call Type:',
|
||||
value: history.callType,
|
||||
),
|
||||
DetailRow(
|
||||
label: 'Call Status:',
|
||||
value: history.callStatus,
|
||||
),
|
||||
DetailRow(
|
||||
label: 'Date:',
|
||||
value: DateFormat('MMM dd, yyyy - hh:mm a').format(history.date),
|
||||
),
|
||||
DetailRow(
|
||||
label: 'Attempts:',
|
||||
value: '${history.attempts}',
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// If you have more details like duration, contact number, etc.
|
||||
if (contact.phones.isNotEmpty)
|
||||
DetailRow(
|
||||
label: 'Number:',
|
||||
value: contact.phones.first.number,
|
||||
return ListTile(
|
||||
leading: (contact.thumbnail != null && contact.thumbnail!.isNotEmpty)
|
||||
? CircleAvatar(
|
||||
backgroundImage: MemoryImage(contact.thumbnail!),
|
||||
)
|
||||
: CircleAvatar(
|
||||
child: Text(
|
||||
contact.displayName.isNotEmpty
|
||||
? contact.displayName[0]
|
||||
: '?',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DetailRow extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
|
||||
const DetailRow({Key? key, required this.label, required this.value}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(color: Colors.white70, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
],
|
||||
title: Text(
|
||||
contact.displayName,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
subtitle: Text(
|
||||
'${history.callType} - ${history.callStatus} - ${DateFormat('MMM dd, hh:mm a').format(history.date)}',
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
trailing: Text(
|
||||
'${history.attempts}x',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
onTap: () {
|
||||
// Handle tap event if needed
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -5,28 +5,26 @@ import 'package:dialer/features/history/history_page.dart';
|
||||
import 'package:dialer/features/composition/composition.dart';
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
import 'package:dialer/features/settings/settings.dart';
|
||||
import '../../widgets/contact_service.dart';
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
List<Contact> _allContacts = [];
|
||||
List<Contact> _contactSuggestions = [];
|
||||
final ContactService _contactService = ContactService();
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Set the TabController length to 3
|
||||
_tabController = TabController(length: 3, vsync: this, initialIndex: 1);
|
||||
_tabController = TabController(length: 4, vsync: this, initialIndex: 1);
|
||||
_tabController.addListener(_handleTabIndex);
|
||||
_fetchContacts();
|
||||
}
|
||||
|
||||
void _fetchContacts() async {
|
||||
_allContacts = await _contactService.fetchContacts();
|
||||
setState(() {});
|
||||
if (await FlutterContacts.requestPermission()) {
|
||||
_allContacts = await FlutterContacts.getContacts(withProperties: true);
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
void _onSearchChanged(String query) {
|
||||
@ -70,97 +68,68 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(255, 30, 30, 30),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
border: Border(
|
||||
top: BorderSide(color: Colors.grey.shade800, width: 1),
|
||||
left: BorderSide(color: Colors.grey.shade800, width: 1),
|
||||
right: BorderSide(color: Colors.grey.shade800, width: 1),
|
||||
bottom:
|
||||
BorderSide(color: Colors.grey.shade800, width: 2),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(255, 30, 30, 30),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
border: Border(
|
||||
top: BorderSide(color: Colors.grey.shade800, width: 1),
|
||||
left: BorderSide(color: Colors.grey.shade800, width: 1),
|
||||
right: BorderSide(color: Colors.grey.shade800, width: 1),
|
||||
bottom: BorderSide(color: Colors.grey.shade800, width: 2),
|
||||
),
|
||||
),
|
||||
child: SearchAnchor(
|
||||
builder: (BuildContext context, SearchController controller) {
|
||||
return SearchBar(
|
||||
controller: controller,
|
||||
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
|
||||
const EdgeInsets.only(
|
||||
top: 6.0,
|
||||
bottom: 6.0,
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
),
|
||||
),
|
||||
child: SearchAnchor(
|
||||
builder:
|
||||
(BuildContext context, SearchController controller) {
|
||||
return SearchBar(
|
||||
controller: controller,
|
||||
padding:
|
||||
MaterialStateProperty.all<EdgeInsetsGeometry>(
|
||||
const EdgeInsets.only(
|
||||
top: 6.0,
|
||||
bottom: 6.0,
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
controller.openView();
|
||||
_onSearchChanged('');
|
||||
},
|
||||
backgroundColor: MaterialStateProperty.all(
|
||||
const Color.fromARGB(255, 30, 30, 30)),
|
||||
hintText: 'Search contacts',
|
||||
hintStyle: MaterialStateProperty.all(
|
||||
const TextStyle(color: Colors.grey, fontSize: 16.0),
|
||||
),
|
||||
leading: const Icon(
|
||||
Icons.search,
|
||||
color: Colors.grey,
|
||||
size: 24.0,
|
||||
),
|
||||
shape:
|
||||
MaterialStateProperty.all<RoundedRectangleBorder>(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
viewOnChanged: (query) {
|
||||
_onSearchChanged(query);
|
||||
},
|
||||
suggestionsBuilder:
|
||||
(BuildContext context, SearchController controller) {
|
||||
return _contactSuggestions.map((contact) {
|
||||
return ListTile(
|
||||
key: ValueKey(contact.id),
|
||||
title: Text(contact.displayName,
|
||||
style: const TextStyle(color: Colors.white)),
|
||||
onTap: () {
|
||||
controller.closeView(contact.displayName);
|
||||
},
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
onTap: () {
|
||||
controller.openView();
|
||||
_onSearchChanged('');
|
||||
},
|
||||
backgroundColor: MaterialStateProperty.all(
|
||||
const Color.fromARGB(255, 30, 30, 30)),
|
||||
hintText: 'Search contacts',
|
||||
hintStyle: MaterialStateProperty.all(
|
||||
const TextStyle(color: Colors.grey, fontSize: 16.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 3-dot menu
|
||||
PopupMenuButton<String>(
|
||||
icon: const Icon(Icons.more_vert, color: Colors.white),
|
||||
itemBuilder: (BuildContext context) => [
|
||||
const PopupMenuItem<String>(
|
||||
value: 'settings',
|
||||
child: Text('Settings'),
|
||||
leading: const Icon(
|
||||
Icons.search,
|
||||
color: Colors.grey,
|
||||
size: 24.0,
|
||||
),
|
||||
],
|
||||
onSelected: (String value) {
|
||||
if (value == 'settings') {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SettingsPage()),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
viewOnChanged: (query) {
|
||||
_onSearchChanged(query);
|
||||
},
|
||||
suggestionsBuilder:
|
||||
(BuildContext context, SearchController controller) {
|
||||
return _contactSuggestions.map((contact) {
|
||||
return ListTile(
|
||||
key: ValueKey(contact.id),
|
||||
title: Text(contact.displayName,
|
||||
style: const TextStyle(color: Colors.white)),
|
||||
onTap: () {
|
||||
controller.closeView(contact.displayName);
|
||||
},
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
// Main content with TabBarView
|
||||
@ -170,9 +139,10 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
TabBarView(
|
||||
controller: _tabController,
|
||||
children: const [
|
||||
FavoritesPage(),
|
||||
FavoritePage(),
|
||||
HistoryPage(),
|
||||
ContactPage(),
|
||||
SettingsPage(), // Add your SettingsPage here
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
@ -216,6 +186,10 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
icon: Icon(_tabController.index == 2
|
||||
? Icons.contacts
|
||||
: Icons.contacts_outlined)),
|
||||
Tab(
|
||||
icon: Icon(_tabController.index == 3 // Corrected index
|
||||
? Icons.settings
|
||||
: Icons.settings_outlined)),
|
||||
],
|
||||
labelColor: Colors.white,
|
||||
unselectedLabelColor: const Color.fromARGB(255, 158, 158, 158),
|
||||
|
@ -1,167 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class BlockedNumbersPage extends StatefulWidget {
|
||||
const BlockedNumbersPage({super.key});
|
||||
|
||||
@override
|
||||
_BlockedNumbersPageState createState() => _BlockedNumbersPageState();
|
||||
}
|
||||
|
||||
class _BlockedNumbersPageState extends State<BlockedNumbersPage> {
|
||||
bool _blockUnknownNumbers = false; // Toggle for blocking unknown numbers
|
||||
List<String> _blockedNumbers = []; // List of blocked numbers
|
||||
final TextEditingController _numberController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadPreferences(); // Load data on initialization
|
||||
}
|
||||
|
||||
// Load preferences from local storage
|
||||
Future<void> _loadPreferences() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
setState(() {
|
||||
_blockUnknownNumbers = prefs.getBool('blockUnknownNumbers') ?? false;
|
||||
_blockedNumbers = prefs.getStringList('blockedNumbers') ?? [];
|
||||
});
|
||||
}
|
||||
|
||||
// Save preferences to local storage
|
||||
Future<void> _savePreferences() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('blockUnknownNumbers', _blockUnknownNumbers);
|
||||
await prefs.setStringList('blockedNumbers', _blockedNumbers);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: AppBar(
|
||||
title: const Text('Blocked Numbers'),
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
SwitchListTile(
|
||||
title: const Text(
|
||||
'Block Unknown Numbers',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
value: _blockUnknownNumbers,
|
||||
onChanged: (bool value) {
|
||||
setState(() {
|
||||
_blockUnknownNumbers = value;
|
||||
_savePreferences(); // Save the state to local storage
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ListTile(
|
||||
title: const Text(
|
||||
'Blocked Numbers',
|
||||
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
subtitle: _blockedNumbers.isEmpty
|
||||
? const Text(
|
||||
'No blocked numbers',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
..._blockedNumbers.map(
|
||||
(number) => ListTile(
|
||||
title: Text(
|
||||
number,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.delete, color: Colors.red),
|
||||
onPressed: () => _unblockNumber(number),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(color: Colors.grey),
|
||||
ListTile(
|
||||
title: const Text(
|
||||
'Block a Number',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
trailing: const Icon(Icons.add, color: Colors.white),
|
||||
onTap: () => _showBlockNumberDialog(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Function to block a number
|
||||
void _blockNumber(String number) {
|
||||
if (number.isNotEmpty && !_blockedNumbers.contains(number)) {
|
||||
setState(() {
|
||||
_blockedNumbers.add(number);
|
||||
_savePreferences(); // Save the updated list
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('$number has been blocked')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to unblock a number
|
||||
void _unblockNumber(String number) {
|
||||
setState(() {
|
||||
_blockedNumbers.remove(number);
|
||||
_savePreferences(); // Save the updated list
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('$number has been unblocked')),
|
||||
);
|
||||
}
|
||||
|
||||
// Dialog for blocking a new number
|
||||
void _showBlockNumberDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: Colors.grey[900],
|
||||
title: const Text('Block a Number', style: TextStyle(color: Colors.white)),
|
||||
content: TextField(
|
||||
controller: _numberController,
|
||||
keyboardType: TextInputType.phone,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter number',
|
||||
hintStyle: TextStyle(color: Colors.grey),
|
||||
),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text('Cancel', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_blockNumber(_numberController.text);
|
||||
_numberController.clear();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text('Block', style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_numberController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -1,19 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'key_storage.dart';
|
||||
|
||||
class DeleteKeyPairPage extends StatelessWidget {
|
||||
const DeleteKeyPairPage({super.key});
|
||||
|
||||
Future<void> _deleteKeyPair(BuildContext context) async {
|
||||
final keyStorage = KeyStorage();
|
||||
await keyStorage.deleteKeys();
|
||||
void _deleteKeyPair(BuildContext context) {
|
||||
// Key deletion logic (not implemented here)
|
||||
// ...
|
||||
|
||||
// Show confirmation message
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('The key pair has been deleted.'),
|
||||
),
|
||||
);
|
||||
|
||||
// Navigate back or update the UI as needed
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,6 @@ import 'dart:typed_data';
|
||||
import 'dart:convert';
|
||||
import 'package:pointycastle/export.dart' as crypto;
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'dart:io';
|
||||
import 'key_storage.dart';
|
||||
|
||||
class ExportPrivateKeyPage extends StatefulWidget {
|
||||
const ExportPrivateKeyPage({super.key});
|
||||
@ -17,81 +15,55 @@ class _ExportPrivateKeyPageState extends State<ExportPrivateKeyPage> {
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
|
||||
Future<void> _exportPrivateKey() async {
|
||||
final keyStorage = KeyStorage();
|
||||
final privateKeyPem = await keyStorage.getPrivateKey();
|
||||
|
||||
if (privateKeyPem == null) {
|
||||
// Show error message if there's no key
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('No private key found to export.'),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Replace with your actual private key retrieval logic
|
||||
final String privateKeyPem = 'Your private key here';
|
||||
|
||||
// Get the password from the user input
|
||||
final password = _passwordController.text;
|
||||
if (password.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Please enter a password.'),
|
||||
),
|
||||
);
|
||||
// Show error message
|
||||
return;
|
||||
}
|
||||
|
||||
// Encrypt the private key using AES-256
|
||||
final encryptedData = _encryptPrivateKey(privateKeyPem, password);
|
||||
|
||||
// Let the user pick a file location
|
||||
final outputFile = await FilePicker.platform.saveFile(
|
||||
dialogTitle: 'Save encrypted private key',
|
||||
fileName: 'private_key_encrypted.aes',
|
||||
);
|
||||
|
||||
if (outputFile != null) {
|
||||
try {
|
||||
final file = File(outputFile);
|
||||
await file.writeAsBytes(encryptedData);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Key Exported'),
|
||||
content: const Text('The encrypted private key has been exported successfully.'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Failed to write file: $e'),
|
||||
),
|
||||
);
|
||||
}
|
||||
// Write the encrypted data to the file
|
||||
// Use appropriate file I/O methods (not shown here)
|
||||
// ...
|
||||
// Show a confirmation dialog or message
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Key Exported'),
|
||||
content: const Text('The encrypted private key has been exported successfully.'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List _encryptPrivateKey(String privateKey, String password) {
|
||||
// Derive a key from the password using PBKDF2
|
||||
final derivator = crypto.PBKDF2KeyDerivator(
|
||||
crypto.HMac(crypto.SHA256Digest(), 64),
|
||||
);
|
||||
// Encryption logic using AES-256
|
||||
final key = crypto.PBKDF2KeyDerivator(crypto.HMac(crypto.SHA256Digest(), 64))
|
||||
.process(Uint8List.fromList(utf8.encode(password)));
|
||||
|
||||
final salt = Uint8List.fromList(utf8.encode('some_salt')); // In production, use a random salt and store it securely
|
||||
derivator.init(crypto.Pbkdf2Parameters(salt, 1000, 32));
|
||||
final key = derivator.process(Uint8List.fromList(utf8.encode(password)));
|
||||
|
||||
// Initialize AES-CBC cipher with PKCS7 padding
|
||||
final iv = Uint8List(16); // zero IV for example, in production use random IV and store it
|
||||
final params = crypto.PaddedBlockCipherParameters<crypto.ParametersWithIV<crypto.KeyParameter>, Null>(
|
||||
crypto.ParametersWithIV<crypto.KeyParameter>(crypto.KeyParameter(key), iv),
|
||||
final params = crypto.PaddedBlockCipherParameters(
|
||||
crypto.ParametersWithIV(crypto.KeyParameter(key), Uint8List(16)), // Initialization Vector
|
||||
null,
|
||||
);
|
||||
|
||||
final cipher = crypto.PaddedBlockCipher('AES/CBC/PKCS7');
|
||||
cipher.init(true, params);
|
||||
|
||||
|
@ -4,12 +4,12 @@ import 'dart:math';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:asn1lib/asn1lib.dart';
|
||||
import 'key_storage.dart';
|
||||
|
||||
class GenerateNewKeyPairPage extends StatelessWidget {
|
||||
const GenerateNewKeyPairPage({super.key});
|
||||
|
||||
Future<Map<String, String>> _generateKeyPair() async {
|
||||
// Key generation logic using pointycastle
|
||||
final keyParams = crypto.RSAKeyGeneratorParameters(
|
||||
BigInt.parse('65537'),
|
||||
2048,
|
||||
@ -17,6 +17,8 @@ class GenerateNewKeyPairPage extends StatelessWidget {
|
||||
);
|
||||
|
||||
final secureRandom = crypto.FortunaRandom();
|
||||
|
||||
// Seed the random number generator
|
||||
final random = Random.secure();
|
||||
final seeds = List<int>.generate(32, (_) => random.nextInt(256));
|
||||
secureRandom.seed(crypto.KeyParameter(Uint8List.fromList(seeds)));
|
||||
@ -29,12 +31,11 @@ class GenerateNewKeyPairPage extends StatelessWidget {
|
||||
final publicKey = pair.publicKey as crypto.RSAPublicKey;
|
||||
final privateKey = pair.privateKey as crypto.RSAPrivateKey;
|
||||
|
||||
// Convert keys to PEM format
|
||||
final publicKeyPem = _encodePublicKeyToPemPKCS1(publicKey);
|
||||
final privateKeyPem = _encodePrivateKeyToPemPKCS1(privateKey);
|
||||
|
||||
// Save keys securely
|
||||
final keyStorage = KeyStorage();
|
||||
await keyStorage.saveKeys(publicKey: publicKeyPem, privateKey: privateKeyPem);
|
||||
// Save keys securely (not implemented here)
|
||||
|
||||
return {'publicKey': publicKeyPem, 'privateKey': privateKeyPem};
|
||||
}
|
||||
@ -51,8 +52,7 @@ class GenerateNewKeyPairPage extends StatelessWidget {
|
||||
|
||||
Uint8List _encodePublicKeyToDer(crypto.RSAPublicKey publicKey) {
|
||||
final algorithmSeq = ASN1Sequence();
|
||||
// Create the OID directly with the arcs
|
||||
algorithmSeq.add(ASN1ObjectIdentifier([1, 2, 840, 113549, 1, 1, 1]));
|
||||
algorithmSeq.add(ASN1ObjectIdentifier.fromName('rsaEncryption'));
|
||||
algorithmSeq.add(ASN1Null());
|
||||
|
||||
final publicKeySeq = ASN1Sequence();
|
||||
@ -84,8 +84,8 @@ class GenerateNewKeyPairPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
String _formatPem(Uint8List bytes, String label) {
|
||||
final base64Data = base64Encode(bytes);
|
||||
final chunks = RegExp('.{1,64}').allMatches(base64Data).map((m) => m.group(0)!);
|
||||
final base64 = base64Encode(bytes);
|
||||
final chunks = RegExp('.{1,64}').allMatches(base64).map((m) => m.group(0)!);
|
||||
return '-----BEGIN $label-----\n${chunks.join('\n')}\n-----END $label-----';
|
||||
}
|
||||
|
||||
@ -99,12 +99,13 @@ class GenerateNewKeyPairPage extends StatelessWidget {
|
||||
body: Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
await _generateKeyPair();
|
||||
final keys = await _generateKeyPair();
|
||||
// Display a confirmation dialog or message
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Keys Generated'),
|
||||
content: const Text('The new key pair has been generated and stored securely.'),
|
||||
content: const Text('The new key pair has been generated successfully.'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
|
@ -1,28 +0,0 @@
|
||||
// key_storage.dart
|
||||
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
class KeyStorage {
|
||||
static const _publicKeyKey = 'public_key';
|
||||
static const _privateKeyKey = 'private_key';
|
||||
|
||||
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
||||
|
||||
Future<void> saveKeys({required String publicKey, required String privateKey}) async {
|
||||
await _storage.write(key: _publicKeyKey, value: publicKey);
|
||||
await _storage.write(key: _privateKeyKey, value: privateKey);
|
||||
}
|
||||
|
||||
Future<String?> getPublicKey() async {
|
||||
return await _storage.read(key: _publicKeyKey);
|
||||
}
|
||||
|
||||
Future<String?> getPrivateKey() async {
|
||||
return await _storage.read(key: _privateKeyKey);
|
||||
}
|
||||
|
||||
Future<void> deleteKeys() async {
|
||||
await _storage.delete(key: _publicKeyKey);
|
||||
await _storage.delete(key: _privateKeyKey);
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'show_public_key_qr.dart';
|
||||
import 'show_public_key_text.dart';
|
||||
import 'generate_new_key_pair.dart';
|
||||
import 'export_private_key.dart';
|
||||
import 'delete_key_pair.dart';
|
||||
import 'package:dialer/features/settings/key/show_public_key_qr.dart';
|
||||
import 'package:dialer/features/settings/key/show_public_key_text.dart';
|
||||
import 'package:dialer/features/settings/key/generate_new_key_pair.dart';
|
||||
import 'package:dialer/features/settings/key/export_private_key.dart';
|
||||
import 'package:dialer/features/settings/key/delete_key_pair.dart';
|
||||
|
||||
class KeyManagementPage extends StatelessWidget {
|
||||
const KeyManagementPage({super.key});
|
||||
@ -34,13 +34,14 @@ class KeyManagementPage extends StatelessWidget {
|
||||
MaterialPageRoute(builder: (context) => const ExportPrivateKeyPage()),
|
||||
);
|
||||
break;
|
||||
case 'Delete a key pair':
|
||||
case 'Delete a key pair, warning POPUP':
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const DeleteKeyPairPage()),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
// Handle default or unknown options
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -52,7 +53,7 @@ class KeyManagementPage extends StatelessWidget {
|
||||
'Display public key as QR code',
|
||||
'Generate a new key pair',
|
||||
'Export private key to password-encrypted file (AES 256)',
|
||||
'Delete a key pair',
|
||||
'Delete a key pair, warning POPUP',
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
|
@ -1,48 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pretty_qr_code/pretty_qr_code.dart';
|
||||
import 'key_storage.dart';
|
||||
|
||||
class DisplayPublicKeyQRCodePage extends StatelessWidget {
|
||||
const DisplayPublicKeyQRCodePage({super.key});
|
||||
|
||||
Future<String?> _loadPublicKey() async {
|
||||
final keyStorage = KeyStorage();
|
||||
return keyStorage.getPublicKey();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Replace with your actual public key retrieval logic
|
||||
final String publicKey = 'Your public key here';
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: AppBar(
|
||||
title: const Text('Public Key in QR Code'),
|
||||
),
|
||||
body: FutureBuilder<String?>(
|
||||
future: _loadPublicKey(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final publicKey = snapshot.data;
|
||||
if (publicKey == null) {
|
||||
return const Center(
|
||||
child: Text(
|
||||
'No public key found.',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: PrettyQr(
|
||||
data: publicKey,
|
||||
size: 250,
|
||||
roundEdges: true,
|
||||
elementColor: Colors.white,
|
||||
),
|
||||
);
|
||||
},
|
||||
body: Center(
|
||||
child: PrettyQr(
|
||||
data: publicKey,
|
||||
size: 250,
|
||||
roundEdges: true,
|
||||
elementColor: Colors.white,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,49 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'key_storage.dart';
|
||||
|
||||
class DisplayPublicKeyTextPage extends StatelessWidget {
|
||||
const DisplayPublicKeyTextPage({super.key});
|
||||
|
||||
Future<String?> _loadPublicKey() async {
|
||||
final keyStorage = KeyStorage();
|
||||
return await keyStorage.getPublicKey();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Replace with your actual public key retrieval logic
|
||||
final String publicKey = 'Your public key here';
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: AppBar(
|
||||
title: const Text('Public Key as Text'),
|
||||
),
|
||||
body: FutureBuilder<String?>(
|
||||
future: _loadPublicKey(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final publicKey = snapshot.data;
|
||||
if (publicKey == null) {
|
||||
return const Center(
|
||||
child: Text(
|
||||
'No public key found.',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SelectableText(
|
||||
publicKey,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SelectableText(
|
||||
publicKey,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:dialer/features/settings/call/settingsCall.dart';
|
||||
import 'package:dialer/features/settings/sim/settings_accounts.dart';
|
||||
import 'package:dialer/features/settings/key/manage_keys_page.dart';
|
||||
import 'package:dialer/features/settings/blocked/settings_blocked.dart';
|
||||
|
||||
class SettingsPage extends StatelessWidget {
|
||||
const SettingsPage({super.key});
|
||||
@ -17,27 +16,21 @@ class SettingsPage extends StatelessWidget {
|
||||
MaterialPageRoute(builder: (context) => const SettingsCallPage()),
|
||||
);
|
||||
break;
|
||||
case 'Sim settings':
|
||||
case 'Page of telephone accounts':
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const SettingsAccountsPage()),
|
||||
);
|
||||
break;
|
||||
case 'Key management':
|
||||
case 'Gestion de clés':
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const KeyManagementPage()),
|
||||
);
|
||||
break;
|
||||
case 'Blocked numbers':
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const BlockedNumbersPage()),
|
||||
);
|
||||
break;
|
||||
// Add more cases for other settings pages
|
||||
// Add more cases for other settings pages
|
||||
default:
|
||||
// Handle default or unknown settings
|
||||
// Handle default or unknown settings
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -48,13 +41,12 @@ class SettingsPage extends StatelessWidget {
|
||||
'Calling settings',
|
||||
'Page of telephone accounts',
|
||||
'Key management',
|
||||
'Blocked numbers'
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: AppBar(
|
||||
title: const Text('Settings'),
|
||||
title: const Text('settings'),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemCount: settingsOptions.length,
|
||||
|
@ -7,13 +7,13 @@ class SettingsAccountsPage extends StatelessWidget {
|
||||
|
||||
void _navigateToAccountOption(BuildContext context, String option) {
|
||||
switch (option) {
|
||||
case 'Chose SIM card':
|
||||
case 'Choisir la SIM':
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const ChooseSimPage()),
|
||||
);
|
||||
break;
|
||||
case 'SIM card parameters':
|
||||
case 'Paramètre SIM':
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const SimParametersPage()),
|
||||
@ -27,14 +27,14 @@ class SettingsAccountsPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final accountOptions = [
|
||||
'Chose SIM card',
|
||||
'SIM card parameters',
|
||||
'Choisir la SIM',
|
||||
'Paramètre SIM',
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: AppBar(
|
||||
title: const Text('Sim settings'),
|
||||
title: const Text('Page des comptes téléphoniques'),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemCount: accountOptions.length,
|
||||
|
@ -16,7 +16,7 @@ class MyApp extends StatelessWidget {
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.dark
|
||||
),
|
||||
home: SafeArea(child: MyHomePage()),
|
||||
home: const MyHomePage(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -1,52 +0,0 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class BlockService {
|
||||
static final BlockService _instance = BlockService._internal();
|
||||
|
||||
factory BlockService() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
BlockService._internal();
|
||||
|
||||
// Function to add a number to the blocked list
|
||||
Future<void> blockNumber(String number) async {
|
||||
if (number.isEmpty) return;
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
List<String> blockedNumbers = prefs.getStringList('blockedNumbers') ?? [];
|
||||
|
||||
if (!blockedNumbers.contains(number)) {
|
||||
blockedNumbers.add(number);
|
||||
await prefs.setStringList('blockedNumbers', blockedNumbers);
|
||||
print('$number has been blocked');
|
||||
} else {
|
||||
print('$number is already blocked');
|
||||
}
|
||||
}
|
||||
|
||||
// Function to remove a number from the blocked list
|
||||
Future<void> unblockNumber(String number) async {
|
||||
if (number.isEmpty) return;
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
List<String> blockedNumbers = prefs.getStringList('blockedNumbers') ?? [];
|
||||
|
||||
if (blockedNumbers.contains(number)) {
|
||||
blockedNumbers.remove(number);
|
||||
await prefs.setStringList('blockedNumbers', blockedNumbers);
|
||||
print('$number has been unblocked');
|
||||
} else {
|
||||
print('$number is not blocked');
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a number is blocked
|
||||
Future<bool> isNumberBlocked(String number) async {
|
||||
if (number.isEmpty) return false;
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
List<String> blockedNumbers = prefs.getStringList('blockedNumbers') ?? [];
|
||||
return blockedNumbers.contains(number);
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
|
||||
// Service to manage contact-related operations
|
||||
class ContactService {
|
||||
Future<List<Contact>> fetchContacts() async {
|
||||
if (await FlutterContacts.requestPermission()) {
|
||||
return await FlutterContacts.getContacts(
|
||||
withProperties: true,
|
||||
withThumbnail: true,
|
||||
withAccounts: true,
|
||||
withGroups: true,
|
||||
withPhoto: true);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
Future<List<Contact>> fetchFavoriteContacts() async {
|
||||
// Fetch all contacts
|
||||
List<Contact> contacts = await fetchContacts();
|
||||
|
||||
// Filter contacts to only include those with isStarred: true
|
||||
List<Contact> favoriteContacts =
|
||||
contacts.where((contact) => contact.isStarred).toList();
|
||||
|
||||
return favoriteContacts;
|
||||
}
|
||||
|
||||
Future<void> addNewContact(Contact contact) async {
|
||||
await FlutterContacts.insertContact(contact);
|
||||
}
|
||||
}
|
@ -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>
|
7
dialer/macos/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
@ -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>
|
@ -34,7 +34,6 @@ dependencies:
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
shared_preferences: ^2.3.3 # Local storage (no critical data)
|
||||
cupertino_icons: ^1.0.8
|
||||
flutter_contacts: ^1.1.9+2
|
||||
permission_handler: ^11.3.1 # For handling permissions
|
||||
@ -45,11 +44,9 @@ dependencies:
|
||||
mobile_scanner: ^6.0.2
|
||||
pretty_qr_code: ^3.3.0
|
||||
pointycastle: ^3.4.0
|
||||
file_picker: ^8.1.6
|
||||
file_picker: ^5.2.5
|
||||
asn1lib: ^1.0.0
|
||||
intl_utils: ^2.0.7
|
||||
url_launcher: ^6.3.1
|
||||
flutter_secure_storage: ^9.0.0
|
||||
mobile_number:
|
||||
path: packages/mobile_number
|
||||
|
||||
|
5
docs/.gitignore
vendored
@ -1,5 +0,0 @@
|
||||
*
|
||||
!*.md
|
||||
!*.pdf
|
||||
!*.sh
|
||||
!.gitignore
|
@ -1,72 +0,0 @@
|
||||
# Icing
|
||||
|
||||
An Epitech Innovation Project
|
||||
|
||||
*By*
|
||||
**Bartosz Michalak - Alexis Danlos - Florian Griffon - Ange Duhayon - Stéphane Corbière**
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
- [Introduction to Icing](#introducingtoicing)
|
||||
- [Strategy](#icingsstrategy)
|
||||
- [Technology choices]()
|
||||
|
||||
---
|
||||
|
||||
## Introduction to Icing
|
||||
|
||||
Icing is the name of our project, which is divided in **two interconnected goals**:
|
||||
1. Provide an end-to-end (E2E) encryption **code library**, based on Eliptic Curve Cryptography (ECC), to encrypt phone-calls on an **analog audio** level.
|
||||
2. Provide a reference implementation in the form of a totally seamless Android **smartphone dialer** application, that anybody could use without being aware of its encryption feature.
|
||||
|
||||
This idea came naturally to our minds, when we remarked the lack of such tool.
|
||||
|
||||
Where "private messaging" and other "encrypted communication" apps flourish, nowadays, they **all** require an internet access to work.
|
||||
|
||||
### Privacy and security in telecoms should not depend on internet availability.
|
||||
|
||||
We are conscious that ourselves, and our surroundings, grew up in Global North, with simple and cheap internet and telecommunication access, but we should not forget that on a global point of view, it is estimated that less than 20% of the world's stepable land is covered with 3G/4G/+ network.
|
||||
Standard "low-tech" GSM network coverage is almost twice that.
|
||||
|
||||
So in a real-world, stressful and harsh condition, affording privacy or security in telecommunication is usually too much of a luxury; and we should change that.
|
||||
|
||||
Our solution is for the every-man that is not even aware of its smart phone weakness, as well as for the activists or journalists surviving in hostile environment around the globe.
|
||||
|
||||
|
||||
### Setting a new security standard
|
||||
|
||||
#### ***"There is no way to create a backdoor that only the good guys can walk through"***
|
||||
> (*Meredith Whittaker - President of Signal Fundation - July 2023, Channel 4*)
|
||||
|
||||
If the police can listen to your calls with a mandate, hackers can, without mandate.
|
||||
|
||||
Many online platforms, such as online bank accounts, uses phone calls, or voicemails to drop security codes needed for authentication. The idea is to bring extra security, by requiring a second factor to authenticate the user, but most voicemails security features have been obsolete for a long time now.
|
||||
|
||||
**But this could change with globalized end-to-end encryption.**
|
||||
|
||||
This not only enables obfuscation of the transmitted audio data, but also hard peer authentication.
|
||||
This means that if you are in an important call, where you could communicate sensitive information such as passwords, or financial orders, using Icing protocol you and your peer would know that there is no man in the middle, listening and stealing information, and that your correspondent really is who it says.
|
||||
|
||||
---
|
||||
|
||||
### Icing's strategy
|
||||
|
||||
We focus on FOSS community as a primary target.
|
||||
|
||||
Our reference implementation, the Iced dialer, is destined to replace any stock dialer app from any android smartphone.
|
||||
|
||||
Alternative open-source and privacy-focused Android distributions, such as GrapheneOS, are major targets.
|
||||
|
||||
Their community are thriving, and could help our open-source development.
|
||||
|
||||
---
|
||||
|
||||
### Technology choices
|
||||
|
||||
We chose to code with Flutter, the Dart framework.
|
||||
Even though this choice gives us quick-delivery capabilities, we will need to switch language for lower levels development, such as sound stream caption, encryption, compression, encoding, and transmission.
|
||||
|
||||
The language for these manoeuvres is not determined yet, but Go, Rust, Kotlin and Java are good candidates.
|
||||
|
@ -1,90 +0,0 @@
|
||||
---
|
||||
marp: true
|
||||
_class: lead
|
||||
paginate: true
|
||||
---
|
||||
<!-- theme: uncover -->
|
||||
<!-- class: invert -->
|
||||
|
||||
# Icing
|
||||
|
||||
#### Epitech Inovative Project
|
||||
|
||||
##### https://git.gmoker.com/icing
|
||||
|
||||
---
|
||||
|
||||
**Florian** Griffon
|
||||
|
||||
**Bartosz** Michalak
|
||||
|
||||
**Ange** Duhayon
|
||||
|
||||
**Alexis** Danlos
|
||||
|
||||
**Stéphane** Corbière
|
||||
|
||||
---
|
||||
|
||||
# :phone: :man:
|
||||
|
|
||||
|
|
||||
:smiling_imp:
|
||||
|
|
||||
|
|
||||
# :phone: :woman:
|
||||
|
||||
---
|
||||
|
||||
# :phone: :man:
|
||||
**|** | **|**
|
||||
**|** | **|**
|
||||
**|** | **|** :imp:
|
||||
**|** | **|**
|
||||
**|** | **|**
|
||||
# :phone: :woman:
|
||||
|
||||
---
|
||||
|
||||
## Un client téléphonique comme un autre
|
||||
|
||||
---
|
||||
|
||||
## L'utilisateur est le maître de sa sécurité
|
||||
|
||||
---
|
||||
|
||||
### Partage de contacts par QR codes
|
||||
|
||||
---
|
||||
|
||||
## Intégration harmonieuse d'un chiffrement automatique
|
||||
|
||||
---
|
||||
|
||||
### Protection d'appel téléphoniques =
|
||||
|
||||
##### :white_check_mark: Conservation de vie privée
|
||||
##### :white_check_mark: Protection de données sensibles
|
||||
##### :white_check_mark: Protection d'authentification
|
||||
##### :white_check_mark: Protection de la messagerie
|
||||
|
||||
---
|
||||
|
||||
## **Icing Dialer**
|
||||
|
||||
### =
|
||||
|
||||
### **Icing protocol**
|
||||
|
||||
**+**
|
||||
|
||||
### **Dialer**
|
||||
|
||||
---
|
||||
|
||||
## Icing est un **outil**, pas un produit
|
||||
|
||||
---
|
||||
|
||||
# Merci
|
BIN
docs/Pitch.pdf
@ -1,71 +0,0 @@
|
||||
|
||||
# User Manual
|
||||
|
||||
|
||||
**Utilization documentation.**
|
||||
|
||||
Written with chapters for the average Joe user, security experts, and developers.
|
||||
|
||||
The average-user section is only about what the average-user will know from Icing: its dialer reference implementation.
|
||||
|
||||
The security expert section will cover all the theory behind our reference implementation, and the Icing protocol. This section can serve as an introduction / transition for the next section:
|
||||
|
||||
The developer section will explain our code architecture and concepts, going in-depth inside the reference implementation and the Icing protocol library.
|
||||
This library will have dedicated documentation in this section, so any developer can implement it in any desired way.
|
||||
|
||||
Lastly, as a continuation of the developer section, the Manual Test section will cover our manual testing policy.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
|
||||
|
||||
- [Average User](#averageuser)
|
||||
|
||||
- [Security Expert](#icingsstrategy)
|
||||
|
||||
- [Developer](#developer)
|
||||
|
||||
- [Manual Tests](#manualtests)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## Average User
|
||||
|
||||
|
||||
Use the Icing dialer like your normal dialer, if you can't do that we can't help, you dumb retard lmfao.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## Security Expert
|
||||
|
||||
SecUriTy eXpeRt
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## Developer
|
||||
|
||||
int main;
|
||||
|
||||
---
|
||||
|
||||
## Manual Tests
|
||||
|
||||
1. Call grandpa
|
||||
2. Receive mum call
|
||||
3. Order 150g of 95% pure Bolivian coke without encryption
|
||||
4. Order again but with encryption
|
||||
5. Compare results
|
@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
IMG=docker.io/marpteam/marp-cli:latest
|
||||
|
||||
docker run --rm -v "$PWD:/home/marp/app/" --entrypoint marp-cli.js "$IMG" \
|
||||
./Pitch.md --pdf
|
@ -1 +0,0 @@
|
||||
PROD_URL=icing.gmoker.com
|
42
website/.gitignore
vendored
@ -1,27 +1,23 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/go
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=go
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
### Go ###
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/go
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
@ -1,14 +0,0 @@
|
||||
ARG VER=1.23
|
||||
|
||||
FROM "docker.io/golang:$VER" as build
|
||||
WORKDIR /build/
|
||||
ARG VER
|
||||
COPY main.go .
|
||||
RUN printf "module main\ngo $VER" > go.mod && CGO_ENABLED=0 go build -o /app
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /app /app
|
||||
COPY static/ /static/
|
||||
COPY html/ /html/
|
||||
EXPOSE 3000
|
||||
CMD ["/app"]
|
24
website/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# my-vue-app
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
5
website/babel.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
---
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
@ -1,229 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Icing</title>
|
||||
<!-- <link rel="stylesheet" href="style.css"/> -->
|
||||
<style>
|
||||
/* Theme colors */
|
||||
:root {
|
||||
--primary-color: #000000; /* Green accent color */
|
||||
--background-color: #f5f5f5; /* Light background */
|
||||
--text-color: #333; /* Dark text */
|
||||
--secondary-text-color: #777; /* Secondary text color */
|
||||
}
|
||||
|
||||
/* Base styles */
|
||||
.content {
|
||||
margin: 20px auto;
|
||||
max-width: 900px;
|
||||
padding: 40px;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
border-radius: 8px;
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.5em;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
animation: fadeInDown 1s ease;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.8em;
|
||||
color: var(--primary-color);
|
||||
margin-top: 40px;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
animation: fadeInLeft 1s ease;
|
||||
}
|
||||
|
||||
.section-title::after {
|
||||
content: '';
|
||||
width: 50px;
|
||||
height: 3px;
|
||||
background-color: var(--primary-color);
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
p, li {
|
||||
line-height: 1.6;
|
||||
font-size: 1.1em;
|
||||
animation: fadeIn 1s ease;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-left: 20px;
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.features ul li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.team-list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.team-list li {
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.back-link-container {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
animation: fadeInUp 1s ease;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
text-decoration: none;
|
||||
color: var(--primary-color);
|
||||
font-weight: bold;
|
||||
border: 2px solid var(--primary-color);
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
background-color: var(--primary-color);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
} to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInLeft {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
} to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
} to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
} to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
p, li {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.content {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
p, li {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="description" class="content">
|
||||
<h1 class="title">Project Description</h1>
|
||||
|
||||
<div class="project-overview">
|
||||
<h2 class="section-title">What is Icing?</h2>
|
||||
<p>
|
||||
Icing is a simple, lightweight, and efficient dialer designed to replace your everyday phone app. It ensures end-to-end encryption of telephone communications by implementing a home-made, analogic-based voice encryption. Inspired by SRTP (Secure Real-time Transport Protocol), using ECDH (Elliptic Curve Diffie-Hellman).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="features">
|
||||
<h2 class="section-title">Key Features</h2>
|
||||
<ul>
|
||||
<li><strong>End-to-End Encryption:</strong> Secure your calls with robust encryption protocols.</li>
|
||||
<li><strong>Transparent:</strong> If your peer doesn't use Icing, the call remains completely normal.</li>
|
||||
<li><strong>Analogic-based:</strong> An open-source, exportable, protocol that <strong>works without internet.</strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="how-it-works">
|
||||
<h2 class="section-title">How It Works</h2>
|
||||
<p>
|
||||
Icing generates a cryptographic key pair for you. Share your public key with a neat QR code.
|
||||
</p>
|
||||
<p>
|
||||
During a call between two Icing users, voices are encrypted, compressed, and transmitted via the telephone network using the Icing Acoustic Protocol.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="team">
|
||||
<h2 class="section-title">Our Team</h2>
|
||||
<p>
|
||||
We are a team of five dedicated individuals working on this solution:
|
||||
</p>
|
||||
<ul class="team-list">
|
||||
<li><a href="https://github.com/AlexisDanlos/" target="_blank">Alexis Danlos</a></li>
|
||||
<li><a href="https://github.com/AustralEpitech/" target="_blank">AustralEpitech</a></li>
|
||||
<li><a href="https://github.com/Bartoszkk/" target="_blank">Bartoszkk</a></li>
|
||||
<li><a href="https://github.com/FlorianGRIFFON/" target="_blank">Florian GRIFFON</a></li>
|
||||
<li><a href="https://github.com/STCB/" target="_blank">STCB</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,31 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
.centered {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
font-size: 3em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.no-underline {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="home">
|
||||
<h1 class="centered">
|
||||
<router-link to="/description" class="no-underline">ICING</router-link>
|
||||
</h1>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
19
website/jsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func route(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/style.css" {
|
||||
http.ServeFile(w, r, "/html/style.css")
|
||||
return
|
||||
}
|
||||
if len(r.URL.Path) > len("/static/") && r.URL.Path[:len("/static/")] == "/static/" {
|
||||
http.ServeFile(w, r, r.URL.Path)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/" {
|
||||
http.ServeFile(w, r, "/html/index.html")
|
||||
return
|
||||
}
|
||||
http.ServeFile(w, r, filepath.Join("/html", r.URL.Path + ".html"))
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", route)
|
||||
|
||||
err := http.ListenAndServe(":3000", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
set -o pipefail
|
||||
|
||||
function kapply() {
|
||||
for f in "$@"; do
|
||||
kubectl apply -f \
|
||||
<(envsubst "$(env | xargs printf '$%s ')" < "manifests/$f")
|
||||
done
|
||||
}
|
||||
|
||||
function kcreatesec() {
|
||||
kubectl create secret generic --save-config --dry-run=client -oyaml "$@" | kubectl apply -f-
|
||||
}
|
||||
|
||||
function kcreatecm() {
|
||||
kubectl create configmap --dry-run=client -oyaml "$@" | kubectl apply -f-
|
||||
}
|
||||
|
||||
function kgseckey() {
|
||||
local sec="$1"; shift
|
||||
local key="$1"; shift
|
||||
|
||||
kubectl get secret "$sec" -o jsonpath="{.data.$key}" | base64 -d
|
||||
}
|
||||
|
||||
function kgcmkey() {
|
||||
local cm="$1"; shift
|
||||
local key="$1"; shift
|
||||
|
||||
kubectl get configmap "$cm" -o jsonpath="{.data.$key}"
|
||||
}
|
||||
|
||||
|
||||
kapply common/app.yaml
|
||||
|
||||
kubectl rollout restart deployment app
|
@ -1,5 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
export NB_REPLICAS=1
|
||||
|
||||
. ./manifests/bin/deploy.sh
|
@ -1,5 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
export NB_REPLICAS=3
|
||||
|
||||
. ./manifests/bin/deploy.sh
|
@ -1,64 +0,0 @@
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: app
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- secretName: tls-app
|
||||
hosts:
|
||||
- "$BASE_URL"
|
||||
rules:
|
||||
- host: "$BASE_URL"
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: app
|
||||
port:
|
||||
name: http
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: app
|
||||
labels:
|
||||
app: app
|
||||
spec:
|
||||
selector:
|
||||
app: app
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: app
|
||||
labels:
|
||||
app: app
|
||||
spec:
|
||||
replicas: $NB_REPLICAS
|
||||
selector:
|
||||
matchLabels:
|
||||
app: app
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: app
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: app
|
||||
image: "$IMAGEAPP"
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 3000
|
20230
website/package-lock.json
generated
Normal file
44
website/package.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "my-vue-app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.8.3",
|
||||
"vue": "^3.2.13",
|
||||
"vue-router": "^4.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.16",
|
||||
"@babel/eslint-parser": "^7.12.16",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^8.0.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "@babel/eslint-parser"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead",
|
||||
"not ie 11"
|
||||
]
|
||||
}
|
BIN
website/public/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
17
website/public/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
19
website/src/App.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<!-- App.vue -->
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'App',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Global styles can be added here */
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
BIN
website/src/assets/logo.png
Normal file
After Width: | Height: | Size: 6.7 KiB |