Commit
·
700863c
0
Parent(s):
Initial commit
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .env.example +35 -0
- .gitattributes +2 -0
- .gitignore +40 -0
- README.md +73 -0
- README_GEMINI.md +46 -0
- bias_analyzer.py +470 -0
- check_models.py +21 -0
- gdelt_api.py +193 -0
- gdelt_query_builder.py +202 -0
- google_search.py +64 -0
- main.py +345 -0
- misinformationui/.gitignore +45 -0
- misinformationui/.metadata +45 -0
- misinformationui/README.md +16 -0
- misinformationui/analysis_options.yaml +28 -0
- misinformationui/android/.gitignore +14 -0
- misinformationui/android/app/build.gradle.kts +44 -0
- misinformationui/android/app/src/debug/AndroidManifest.xml +7 -0
- misinformationui/android/app/src/main/AndroidManifest.xml +45 -0
- misinformationui/android/app/src/main/kotlin/com/example/misinformationui/MainActivity.kt +5 -0
- misinformationui/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
- misinformationui/android/app/src/main/res/drawable/launch_background.xml +12 -0
- misinformationui/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +3 -0
- misinformationui/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +3 -0
- misinformationui/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +3 -0
- misinformationui/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +3 -0
- misinformationui/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +3 -0
- misinformationui/android/app/src/main/res/values-night/styles.xml +18 -0
- misinformationui/android/app/src/main/res/values/styles.xml +18 -0
- misinformationui/android/app/src/profile/AndroidManifest.xml +7 -0
- misinformationui/android/build.gradle.kts +21 -0
- misinformationui/android/gradle.properties +3 -0
- misinformationui/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- misinformationui/android/settings.gradle.kts +25 -0
- misinformationui/ios/.gitignore +34 -0
- misinformationui/ios/Flutter/AppFrameworkInfo.plist +26 -0
- misinformationui/ios/Flutter/Debug.xcconfig +1 -0
- misinformationui/ios/Flutter/Release.xcconfig +1 -0
- misinformationui/ios/Runner.xcodeproj/project.pbxproj +616 -0
- misinformationui/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- misinformationui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- misinformationui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- misinformationui/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
- misinformationui/ios/Runner.xcworkspace/contents.xcworkspacedata +7 -0
- misinformationui/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- misinformationui/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- misinformationui/ios/Runner/AppDelegate.swift +13 -0
- misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
- misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected] +3 -0
- misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected] +3 -0
.env.example
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Example .env file for Fake News Detection System
|
2 |
+
# Rename this file to .env and modify values as needed
|
3 |
+
|
4 |
+
# GDELT API configuration
|
5 |
+
# No API key needed for GDELT
|
6 |
+
MAX_ARTICLES_PER_QUERY=250
|
7 |
+
|
8 |
+
# Ranking configuration
|
9 |
+
TOP_K_ARTICLES=250
|
10 |
+
SIMILARITY_MODEL=intfloat/multilingual-e5-base
|
11 |
+
MIN_SIMILARITY_THRESHOLD=0.1
|
12 |
+
|
13 |
+
# Display configuration
|
14 |
+
SHOW_SIMILARITY_SCORES=true
|
15 |
+
SHOW_PUBLISH_DATE=true
|
16 |
+
SHOW_URL=true
|
17 |
+
|
18 |
+
# Domain filtering configuration
|
19 |
+
USE_WHITELIST_ONLY=false
|
20 |
+
|
21 |
+
# Google Gemini API
|
22 |
+
# Get your API key from https://ai.google.dev/
|
23 |
+
GEMINI_API_KEY=your_gemini_api_key_here
|
24 |
+
GEMINI_MODEL=gemini-2.5-flash
|
25 |
+
|
26 |
+
# Google Search API (SerpAPI)
|
27 |
+
# Get your API key from https://serpapi.com/
|
28 |
+
SERPAPI_KEY=your_serpapi_key_here
|
29 |
+
|
30 |
+
# Bias Analysis configuration
|
31 |
+
TOP_ARTICLES_PER_BIAS_CATEGORY=5
|
32 |
+
|
33 |
+
# Server configuration
|
34 |
+
PORT=5000
|
35 |
+
DEBUG=false
|
.gitattributes
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Environment variables
|
2 |
+
.env
|
3 |
+
.env.*
|
4 |
+
!.env.example
|
5 |
+
|
6 |
+
# Python
|
7 |
+
__pycache__/
|
8 |
+
*.py[cod]
|
9 |
+
*$py.class
|
10 |
+
*.so
|
11 |
+
.Python
|
12 |
+
build/
|
13 |
+
develop-eggs/
|
14 |
+
dist/
|
15 |
+
downloads/
|
16 |
+
eggs/
|
17 |
+
.eggs/
|
18 |
+
lib/
|
19 |
+
lib64/
|
20 |
+
parts/
|
21 |
+
sdist/
|
22 |
+
var/
|
23 |
+
wheels/
|
24 |
+
*.egg-info/
|
25 |
+
.installed.cfg
|
26 |
+
*.egg
|
27 |
+
|
28 |
+
# Virtual environments
|
29 |
+
venv/
|
30 |
+
env/
|
31 |
+
ENV/
|
32 |
+
env.bak/
|
33 |
+
venv.bak/
|
34 |
+
|
35 |
+
# IDE files
|
36 |
+
.idea/
|
37 |
+
.vscode/
|
38 |
+
*.swp
|
39 |
+
*.swo
|
40 |
+
.DS_Store
|
README.md
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Fake News Detection (GDELT + Gemini) with Minimal UI
|
2 |
+
|
3 |
+
This app takes a user query, builds robust GDELT queries via Gemini, fetches articles, analyzes outlet bias, ranks articles with local embeddings, and returns a concise, multi‑perspective summary. The UI renders exactly what the backend returns (no extra formatting or hardcoded values).
|
4 |
+
|
5 |
+
## Key Features
|
6 |
+
- Query expansion: 10 GDELT query variations, language-preserving, AND-only operators.
|
7 |
+
- Sensitive-query guard: pornography/religion and similar sensitive topics short‑circuit with “I cannot respond to this query.”
|
8 |
+
- GDELT ingestion and normalization.
|
9 |
+
- Gemini-driven bias analysis with categories; one category is strictly named “unbiased”.
|
10 |
+
- Per-category ranking using a cached local embedding model (SentenceTransformers), shared across requests.
|
11 |
+
- Multi‑perspective summarization:
|
12 |
+
- Sends top URLs from all categories (including unbiased) to Gemini.
|
13 |
+
- Summary lists sources grouped by category with up to 5 URLs per category.
|
14 |
+
- Appends the “reasoning” string (from bias analysis) after the sources.
|
15 |
+
- Optional domain whitelisting (toggle in .env).
|
16 |
+
- Terminal and UI show the exact same summary string.
|
17 |
+
|
18 |
+
## Requirements
|
19 |
+
- Python 3.11+
|
20 |
+
- Conda/venv recommended
|
21 |
+
- Packages: flask, flask-cors, python-dotenv, requests, sentence-transformers, torch, google-generativeai
|
22 |
+
|
23 |
+
## Setup
|
24 |
+
1. Create and activate environment (example with conda):
|
25 |
+
- conda create -n fake_news_detection python=3.11 -y
|
26 |
+
- conda activate fake_news_detection
|
27 |
+
2. Install deps:
|
28 |
+
- pip install -r requirements.txt (if present) or install the packages listed above.
|
29 |
+
3. Copy .env.example to .env and set values:
|
30 |
+
- GEMINI_API_KEY, GEMINI_MODEL (e.g., gemini-1.5-pro or gemini-2.5-pro)
|
31 |
+
- MAX_ARTICLES_PER_QUERY, TOP_N_PER_CATEGORY, MIN_SIMILARITY_THRESHOLD
|
32 |
+
- SIMILARITY_MODEL (e.g., intfloat/multilingual-e5-base)
|
33 |
+
- SHOW_SIMILARITY_SCORES, SHOW_PUBLISH_DATE, SHOW_URL
|
34 |
+
- USE_WHITELIST_ONLY (true/false)
|
35 |
+
- PORT, DEBUG
|
36 |
+
|
37 |
+
## Run
|
38 |
+
- Linux:
|
39 |
+
- chmod +x ./main.py
|
40 |
+
- ./main.py
|
41 |
+
- Visit http://127.0.0.1:5000
|
42 |
+
|
43 |
+
## API
|
44 |
+
POST /api/detect
|
45 |
+
- Body: {"query": "your question"}
|
46 |
+
- Returns (simplified):
|
47 |
+
```
|
48 |
+
{
|
49 |
+
"query": "...",
|
50 |
+
"summary": "MULTI-PERSPECTIVE FACTUAL SUMMARY...\n\n...SOURCES BY CATEGORY...\n\n...REASONING: ...",
|
51 |
+
"status": "ok" | "no_results" | "blocked"
|
52 |
+
}
|
53 |
+
```
|
54 |
+
Notes:
|
55 |
+
- If the query is sensitive, status=blocked and summary contains: “I cannot respond to this query.”
|
56 |
+
- Only the summary string is printed to terminal and sent to UI, and the UI renders it verbatim.
|
57 |
+
|
58 |
+
## Behavior Details
|
59 |
+
- Local embedding model is loaded once and cached for reuse across requests.
|
60 |
+
- Gemini runs in the cloud (no caching).
|
61 |
+
- Bias categories come from Gemini; one is enforced/normalized to exactly “unbiased”.
|
62 |
+
- Summarization uses top URLs from all categories and instructs Gemini to:
|
63 |
+
- Group sources by category,
|
64 |
+
- List up to 5 URLs per category (numbering restarts at 1 inside each category),
|
65 |
+
- Then append the bias-analysis “reasoning” section.
|
66 |
+
|
67 |
+
## Whitelist Filtering
|
68 |
+
- USE_WHITELIST_ONLY=true limits articles to whitelisted domains.
|
69 |
+
- When false, all domains are considered.
|
70 |
+
|
71 |
+
## Frontend
|
72 |
+
- static/ contains a minimal JS client.
|
73 |
+
- It outputs exactly the summary string received from backend (no hardcoded counts/colors/extra text; no horizontal scrolling).
|
README_GEMINI.md
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Gemini Integration: GDELT Query Builder & Summarization
|
2 |
+
|
3 |
+
This project uses Google’s Gemini for:
|
4 |
+
- Building 10 language-preserving GDELT query variations.
|
5 |
+
- Analyzing outlet bias and returning categories (must include exactly “unbiased”).
|
6 |
+
- Producing a multi‑perspective factual summary grouped by bias categories.
|
7 |
+
|
8 |
+
Gemini runs in the cloud and is not cached. The local embedding model is cached and shared across requests.
|
9 |
+
|
10 |
+
## Setup
|
11 |
+
- Get an API key from https://ai.google.dev/
|
12 |
+
- .env:
|
13 |
+
- GEMINI_API_KEY=your_key
|
14 |
+
- GEMINI_MODEL=gemini-1.5-pro (or gemini-2.5-pro / flash variants)
|
15 |
+
|
16 |
+
## Query Builder (gdelt_query_builder.py)
|
17 |
+
- Generates EXACTLY 10 variations separated by |||.
|
18 |
+
- Preserves user language.
|
19 |
+
- Uses AND-only operators between terms.
|
20 |
+
- Adds sourcecountry/sourceregion and datetimes when implied.
|
21 |
+
- Sensitive-query guard:
|
22 |
+
- The system prompt instructs Gemini to return the literal token INAPPROPRIATE_QUERY_DETECTED for sensitive topics (e.g., pornography, explicit adult content, certain religious questions flagged by policy).
|
23 |
+
- The backend detects this and immediately returns a summary “I cannot respond to this query.” (status=blocked).
|
24 |
+
|
25 |
+
Example request body to backend:
|
26 |
+
```
|
27 |
+
{"query": "news about war in Ukraine"}
|
28 |
+
```
|
29 |
+
|
30 |
+
## Bias Analysis
|
31 |
+
- Gemini returns bias categories and counts; one category is normalized to exactly “unbiased”.
|
32 |
+
- Reasoning text explains the categorization logic; this is appended after sources in the final summary.
|
33 |
+
|
34 |
+
## Summarization
|
35 |
+
- Backend sends top URLs from all categories (including unbiased) to Gemini, labeled by category.
|
36 |
+
- Gemini instruction highlights:
|
37 |
+
- Produce a concise factual answer first.
|
38 |
+
- Then list SOURCES BY CATEGORY with up to 5 URLs per category.
|
39 |
+
- Numbering restarts at 1 per category (1–5 for each).
|
40 |
+
- After sources, append “REASONING:” with the bias-analysis reasoning string.
|
41 |
+
- The backend returns only the final formatted summary string; UI renders it verbatim.
|
42 |
+
|
43 |
+
## Notes
|
44 |
+
- No Gemini model caching (cloud API).
|
45 |
+
- Local embedding model (SentenceTransformers) is cached once and reused.
|
46 |
+
- Optional whitelist filtering toggled via USE_WHITELIST_ONLY in .env.
|
bias_analyzer.py
ADDED
@@ -0,0 +1,470 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
bias_analyzer.py - Module for bias analysis, categorization, and summarization
|
3 |
+
|
4 |
+
This module provides functions for:
|
5 |
+
1. Analyzing bias in news sources
|
6 |
+
2. Categorizing articles by bias category
|
7 |
+
3. Creating embeddings for each category
|
8 |
+
4. Summarizing information from unbiased sources
|
9 |
+
"""
|
10 |
+
|
11 |
+
import os
|
12 |
+
import re
|
13 |
+
import json
|
14 |
+
import google.generativeai as genai
|
15 |
+
from dotenv import load_dotenv
|
16 |
+
|
17 |
+
# Import from ranker to use the shared model cache and device detection
|
18 |
+
from ranker import DEVICE, _MODEL_CACHE
|
19 |
+
|
20 |
+
# Load environment variables
|
21 |
+
load_dotenv()
|
22 |
+
|
23 |
+
# Set up the Gemini API with the API key from environment variables
|
24 |
+
GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')
|
25 |
+
if not GEMINI_API_KEY:
|
26 |
+
raise ValueError("GEMINI_API_KEY environment variable not set. Please add it to your .env file.")
|
27 |
+
|
28 |
+
# Initialize the Gemini client
|
29 |
+
genai.configure(api_key=GEMINI_API_KEY)
|
30 |
+
|
31 |
+
|
32 |
+
def analyze_bias(query, outlet_names, model_name):
|
33 |
+
"""
|
34 |
+
Analyzes bias in news sources and categorizes them.
|
35 |
+
|
36 |
+
Args:
|
37 |
+
query (str): The original user query
|
38 |
+
outlet_names (list): List of unique outlet names to categorize
|
39 |
+
model_name (str): The name of the Gemini model to use
|
40 |
+
|
41 |
+
Returns:
|
42 |
+
dict: Dictionary containing bias analysis results and categorized articles
|
43 |
+
"""
|
44 |
+
# Only print status messages if debug mode is enabled
|
45 |
+
print(f"Analyzing potential bias in {len(outlet_names)} unique news outlets...")
|
46 |
+
|
47 |
+
try:
|
48 |
+
# Define system prompt for bias analysis
|
49 |
+
bias_analysis_prompt = """
|
50 |
+
You are an expert media bias analyzer. Your task is to categorize news sources into bias categories based on their reporting styles, focus, and potential biases.
|
51 |
+
|
52 |
+
Analyze the provided list of news outlets in the context of the user's query. Identify any number of distinct bias categories that best describe the potential biases in these sources, plus EXACTLY one "unbiased" category (not "neutral" or any other name). The categories should reflect the relevant dimensions of bias for this specific query and set of outlets.
|
53 |
+
|
54 |
+
For example, depending on the query and outlets, your categories might be:
|
55 |
+
- Query about climate change: "industry-funded", "environmental-activist", "unbiased"
|
56 |
+
- Query about international conflict: "pro-western", "state-controlled", "anti-western", "regional-perspective", "unbiased"
|
57 |
+
- Query about economic policy: "pro-business", "labor-oriented", "progressive", "conservative", "unbiased"
|
58 |
+
|
59 |
+
Consider these factors:
|
60 |
+
- Historical reporting patterns and perspectives
|
61 |
+
- Ownership and financial interests
|
62 |
+
- Terminology and framing used in headlines
|
63 |
+
- Fact-based vs. opinion-heavy reporting
|
64 |
+
- Regional or national interests that may influence coverage
|
65 |
+
|
66 |
+
CRITICAL REQUIREMENT: One category MUST be exactly named "unbiased" (not "neutral", "balanced", "centrist", or any other variation).
|
67 |
+
|
68 |
+
Return your response in the exact JSON format shown below:
|
69 |
+
{
|
70 |
+
"categories": {
|
71 |
+
"bias category 1": [list of outlet names in this category],
|
72 |
+
"bias category 2": [list of outlet names in this category],
|
73 |
+
"bias category 3": [list of outlet names in this category],
|
74 |
+
"bias category 4": [list of outlet names in this category],
|
75 |
+
"unbiased": [list of outlet names that are generally neutral]
|
76 |
+
},
|
77 |
+
"descriptions": {
|
78 |
+
"bias category 1": "A concise description of what this category represents",
|
79 |
+
"bias category 2": "A concise description of what this category represents",
|
80 |
+
"bias category 3": "A concise description of what this category represents",
|
81 |
+
"bias category 4": "A concise description of what this category represents",
|
82 |
+
"unbiased": "A concise description of what this category represents"
|
83 |
+
},
|
84 |
+
"reasoning": "A brief explanation of your overall categorization approach"
|
85 |
+
}
|
86 |
+
|
87 |
+
The number of bias categories can vary based on the query and news sources - create as many distinct categories as needed to accurately represent the different perspectives. You are not limited to just 1 or 2 bias categories - use as many as necessary.
|
88 |
+
|
89 |
+
Replace "bias category 1", "bias category 2", etc. with meaningful names that describe the bias types you've identified.
|
90 |
+
Be comprehensive and put every outlet in exactly one category.
|
91 |
+
IMPORTANT: You MUST name one category exactly "unbiased" (lowercase) without any variation.
|
92 |
+
"""
|
93 |
+
|
94 |
+
# Use the original query directly
|
95 |
+
corrected_query = query
|
96 |
+
|
97 |
+
# Prepare input for Gemini
|
98 |
+
input_text = f"Query: {corrected_query}\n\nNews Sources:\n" + "\n".join(outlet_names)
|
99 |
+
#print(input_text)
|
100 |
+
# Initialize Gemini client and generate analysis
|
101 |
+
model = genai.GenerativeModel(model_name)
|
102 |
+
|
103 |
+
generation_config = genai.GenerationConfig(
|
104 |
+
temperature=0.1, # Lower temperature for more deterministic results
|
105 |
+
)
|
106 |
+
|
107 |
+
response = model.generate_content(
|
108 |
+
f"{bias_analysis_prompt}\n\n{input_text}",
|
109 |
+
generation_config=generation_config
|
110 |
+
)
|
111 |
+
|
112 |
+
# Parse the JSON response
|
113 |
+
response_text = response.text
|
114 |
+
json_match = re.search(r'{[\s\S]*}', response_text)
|
115 |
+
|
116 |
+
if json_match:
|
117 |
+
parsed_response = json.loads(json_match.group(0))
|
118 |
+
|
119 |
+
# Handle the new JSON structure with categories and descriptions
|
120 |
+
bias_analysis = {}
|
121 |
+
category_descriptions = {}
|
122 |
+
|
123 |
+
# Extract categories and descriptions from the new format
|
124 |
+
if 'categories' in parsed_response:
|
125 |
+
# New format
|
126 |
+
categories = parsed_response.get('categories', {})
|
127 |
+
category_descriptions = parsed_response.get('descriptions', {})
|
128 |
+
reasoning = parsed_response.get('reasoning', 'No reasoning provided')
|
129 |
+
|
130 |
+
# Make sure one category is exactly named "unbiased"
|
131 |
+
# If there's a similar category (like "neutral"), rename it to "unbiased"
|
132 |
+
has_unbiased = "unbiased" in categories
|
133 |
+
if not has_unbiased:
|
134 |
+
similar_category = None
|
135 |
+
for cat in list(categories.keys()):
|
136 |
+
if cat.lower() in ["neutral", "center", "balanced", "objective", "impartial"]:
|
137 |
+
similar_category = cat
|
138 |
+
break
|
139 |
+
|
140 |
+
# Rename the similar category to "unbiased" if found
|
141 |
+
if similar_category:
|
142 |
+
categories["unbiased"] = categories.pop(similar_category)
|
143 |
+
if similar_category in category_descriptions:
|
144 |
+
category_descriptions["unbiased"] = category_descriptions.pop(similar_category)
|
145 |
+
|
146 |
+
# Copy categories to the top level for backward compatibility
|
147 |
+
for category, outlets in categories.items():
|
148 |
+
bias_analysis[category] = outlets
|
149 |
+
else:
|
150 |
+
# Old format for backward compatibility
|
151 |
+
for key, value in parsed_response.items():
|
152 |
+
if key != "reasoning":
|
153 |
+
bias_analysis[key] = value
|
154 |
+
reasoning = parsed_response.get('reasoning', 'No reasoning provided')
|
155 |
+
|
156 |
+
# Add reasoning
|
157 |
+
bias_analysis["reasoning"] = reasoning
|
158 |
+
|
159 |
+
# Add descriptions to the analysis result
|
160 |
+
bias_analysis["descriptions"] = category_descriptions
|
161 |
+
|
162 |
+
# Print bias categories
|
163 |
+
print("\nBias Analysis Results:")
|
164 |
+
# Print information about each category
|
165 |
+
for key, values in bias_analysis.items():
|
166 |
+
if key not in ["reasoning", "descriptions"]:
|
167 |
+
print(f"{key}: {len(values)} sources")
|
168 |
+
|
169 |
+
print(f"\nReasoning: {bias_analysis.get('reasoning', 'No reasoning provided')}")
|
170 |
+
|
171 |
+
return bias_analysis
|
172 |
+
else:
|
173 |
+
print("⚠️ Could not parse bias analysis response from Gemini.")
|
174 |
+
# Return an empty analysis with at least unbiased category if parsing fails
|
175 |
+
return {
|
176 |
+
"unbiased": [],
|
177 |
+
"reasoning": "Failed to analyze bias"
|
178 |
+
}
|
179 |
+
|
180 |
+
except Exception as e:
|
181 |
+
print(f"⚠️ Error in bias analysis module: {e}")
|
182 |
+
# Return an empty analysis with at least unbiased category if an error occurs
|
183 |
+
return {
|
184 |
+
"unbiased": [],
|
185 |
+
"reasoning": f"Error in analysis: {str(e)}"
|
186 |
+
}
|
187 |
+
|
188 |
+
|
189 |
+
def categorize_and_rank_by_bias(query, normalized_articles, bias_analysis, ranker, min_threshold):
|
190 |
+
"""
|
191 |
+
Categorizes articles by bias category and creates embeddings for each category.
|
192 |
+
|
193 |
+
Args:
|
194 |
+
query (str): The original user query
|
195 |
+
normalized_articles (list): List of normalized article dictionaries
|
196 |
+
bias_analysis (dict): The bias analysis results
|
197 |
+
ranker: The ArticleRanker instance
|
198 |
+
min_threshold (float): Minimum similarity threshold
|
199 |
+
|
200 |
+
Returns:
|
201 |
+
dict: Dictionary containing ranked articles by category
|
202 |
+
"""
|
203 |
+
# Get number of articles per category from environment variable
|
204 |
+
top_n_per_category = int(os.getenv('TOP_N_PER_CATEGORY', 5))
|
205 |
+
|
206 |
+
# Extract the category names from the bias analysis
|
207 |
+
categories = []
|
208 |
+
for key in bias_analysis.keys():
|
209 |
+
if key not in ["reasoning", "descriptions"]:
|
210 |
+
categories.append(key)
|
211 |
+
|
212 |
+
# Initialize dictionaries for categorized articles
|
213 |
+
categorized_articles = {}
|
214 |
+
for category in categories:
|
215 |
+
categorized_articles[category] = []
|
216 |
+
|
217 |
+
# Categorize articles based on their source
|
218 |
+
for article in normalized_articles:
|
219 |
+
# Extract source name (handling both object and string formats)
|
220 |
+
if isinstance(article['source'], dict):
|
221 |
+
source_name = article['source'].get('name', '')
|
222 |
+
else:
|
223 |
+
source_name = article['source']
|
224 |
+
|
225 |
+
# Check each category to see if this source belongs to it
|
226 |
+
for category in categories:
|
227 |
+
if source_name in bias_analysis.get(category, []):
|
228 |
+
categorized_articles[category].append(article)
|
229 |
+
break
|
230 |
+
|
231 |
+
# Create separate embeddings for each category
|
232 |
+
category_rankings = {}
|
233 |
+
|
234 |
+
for category, articles in categorized_articles.items():
|
235 |
+
if not articles:
|
236 |
+
category_rankings[category] = []
|
237 |
+
continue
|
238 |
+
|
239 |
+
print(f"Creating embeddings for {len(articles)} articles in '{category}' category...")
|
240 |
+
|
241 |
+
# Prepare article texts for this category - ONLY USE TITLES
|
242 |
+
category_texts = [article['title'] for article in articles]
|
243 |
+
|
244 |
+
try:
|
245 |
+
# Create embeddings for this category
|
246 |
+
query_embedding, category_embeddings = ranker.create_embeddings(query, category_texts)
|
247 |
+
|
248 |
+
# Calculate similarities
|
249 |
+
similarities = ranker.calculate_similarities(query_embedding, category_embeddings)
|
250 |
+
|
251 |
+
# Get top articles for this category
|
252 |
+
top_indices = ranker.get_top_articles(
|
253 |
+
similarities,
|
254 |
+
articles,
|
255 |
+
min(top_n_per_category, len(articles)),
|
256 |
+
min_threshold
|
257 |
+
)
|
258 |
+
|
259 |
+
# Format results for this category
|
260 |
+
category_rankings[category] = ranker.format_results(top_indices, similarities, articles)
|
261 |
+
|
262 |
+
# No need to print articles here since they will be printed in main.py
|
263 |
+
|
264 |
+
except Exception as e:
|
265 |
+
print(f"⚠️ Error ranking articles for '{category}' category: {e}")
|
266 |
+
category_rankings[category] = []
|
267 |
+
|
268 |
+
return category_rankings
|
269 |
+
|
270 |
+
|
271 |
+
def generate_summary(query, normalized_articles, category_rankings, model_name):
|
272 |
+
"""
|
273 |
+
Generates a summary using articles from all categories, clearly identifying each category's sources.
|
274 |
+
|
275 |
+
Args:
|
276 |
+
query (str): The original user query
|
277 |
+
normalized_articles (list): List of normalized article dictionaries
|
278 |
+
category_rankings (dict): The ranked articles by category
|
279 |
+
model_name (str): The name of the Gemini model to use
|
280 |
+
|
281 |
+
Returns:
|
282 |
+
str: The generated summary
|
283 |
+
"""
|
284 |
+
# Extract the reasoning from category_rankings if available
|
285 |
+
reasoning = category_rankings.get("reasoning", "No reasoning provided") if isinstance(category_rankings, dict) else "No reasoning provided"
|
286 |
+
|
287 |
+
# Check if we have any articles for summarization
|
288 |
+
if not category_rankings or all(not articles for category, articles in category_rankings.items()
|
289 |
+
if category not in ["descriptions", "reasoning"]):
|
290 |
+
print("No articles available for summarization.")
|
291 |
+
return "No articles available for summarization."
|
292 |
+
|
293 |
+
# Define system prompt for summarization
|
294 |
+
summarization_prompt = """
|
295 |
+
You are an expert news summarizer focused on factual reporting. Your task is to create a concise, factual summary based on multiple news sources from different bias categories.
|
296 |
+
|
297 |
+
The articles provided will be clearly labeled with their bias category. You will receive articles from different perspectives:
|
298 |
+
Articles from the "unbiased" category are generally considered neutral and factual
|
299 |
+
Articles from other categories may represent specific perspectives or biases
|
300 |
+
|
301 |
+
Guidelines:
|
302 |
+
1. Focus primarily on verifiable facts that appear across multiple sources
|
303 |
+
2. Highlight areas of consensus across sources from different categories
|
304 |
+
3. Note significant differences in how different categories report on the same events
|
305 |
+
4. Maintain neutral language in your summary despite potential bias in the sources
|
306 |
+
5. Include relevant dates, figures, and key details
|
307 |
+
6. Prioritize information that directly answers the user's query
|
308 |
+
7. Acknowledge different perspectives when they exist
|
309 |
+
|
310 |
+
IMPORTANT FORMAT INSTRUCTION: Do not use any symbols such as hash (#), asterisk (*), hyphen (-), underscore (_), or any other special characters in your output. Use plain text without any special formatting symbols.
|
311 |
+
|
312 |
+
Structure your response in these sections:
|
313 |
+
1. SUMMARY A 3 to 5 sentence factual answer to the query that balances all perspectives
|
314 |
+
2. KEY FACTS 4 to 6 numbered points with the most important verified information (use numbers only, no symbols)
|
315 |
+
3. DIFFERENT PERSPECTIVES Brief explanation of how different sources frame the issue
|
316 |
+
4. SOURCES BY CATEGORY
|
317 |
+
Group sources under their respective categories (UNBIASED SOURCES, CATEGORY 1 SOURCES, etc.)
|
318 |
+
Under each category heading, list UP TO 5 URLs of sources from that category
|
319 |
+
Number sources starting from 1 within EACH category (each category has its own 1 to 5 numbering)
|
320 |
+
Include only the source name, date, and URL for each source
|
321 |
+
Format: 1. source.com (date) URL https://source.com/article
|
322 |
+
|
323 |
+
IMPORTANT
|
324 |
+
Show each category as a separate heading with the category name in ALL CAPS
|
325 |
+
List all sources from the same category together under their category heading
|
326 |
+
Each category should have its OWN numbering from 1 to 5 (do NOT number continuously across categories)
|
327 |
+
Include URLs for each source, clearly labeled
|
328 |
+
Show up to 5 sources PER CATEGORY (not 5 total)
|
329 |
+
DO NOT use any special characters or symbols such as hash (#), asterisk (*), hyphen (-), underscore (_)
|
330 |
+
|
331 |
+
Be accurate, concise, and provide a balanced view that acknowledges different perspectives.
|
332 |
+
"""
|
333 |
+
|
334 |
+
# We'll limit to a maximum of 30 articles total (to avoid overloading Gemini)
|
335 |
+
# but we'll make sure each category is represented
|
336 |
+
article_info = []
|
337 |
+
article_number = 1
|
338 |
+
max_articles_total = 30
|
339 |
+
|
340 |
+
# Count the number of non-empty categories
|
341 |
+
valid_categories = [cat for cat in category_rankings.keys()
|
342 |
+
if cat not in ["descriptions", "reasoning"] and category_rankings[cat]]
|
343 |
+
|
344 |
+
# Calculate how many articles to take from each category to maintain balance
|
345 |
+
# Ensure we try to get at least 5 per category when possible
|
346 |
+
articles_per_category = min(10, max(5, max_articles_total // len(valid_categories))) if valid_categories else 0
|
347 |
+
|
348 |
+
# Process articles from each category
|
349 |
+
for category, articles in category_rankings.items():
|
350 |
+
# Skip non-category keys
|
351 |
+
if category in ["descriptions", "reasoning"]:
|
352 |
+
continue
|
353 |
+
|
354 |
+
# Skip empty categories
|
355 |
+
if not articles:
|
356 |
+
continue
|
357 |
+
|
358 |
+
# Get limited articles for this category
|
359 |
+
top_articles = articles[:min(articles_per_category, len(articles))]
|
360 |
+
|
361 |
+
# Add category header
|
362 |
+
article_info.append(f"\n===== ARTICLES FROM {category.upper()} CATEGORY =====\n\n")
|
363 |
+
|
364 |
+
# Extract article information for this category
|
365 |
+
for article in top_articles:
|
366 |
+
# Find the full article data from normalized_articles
|
367 |
+
for full_article in normalized_articles:
|
368 |
+
if full_article['url'] == article['url']:
|
369 |
+
# Extract source name (handling both object and string formats)
|
370 |
+
if isinstance(full_article['source'], dict):
|
371 |
+
source_name = full_article['source'].get('name', '')
|
372 |
+
else:
|
373 |
+
source_name = full_article['source']
|
374 |
+
|
375 |
+
# Use the appropriate date field
|
376 |
+
published_date = full_article.get('publishedAt', full_article.get('published_at', ''))
|
377 |
+
|
378 |
+
# Get description content, ensuring we have at least some text
|
379 |
+
description = full_article.get('description', '')
|
380 |
+
if not description and 'content' in full_article:
|
381 |
+
description = full_article['content']
|
382 |
+
if not description:
|
383 |
+
description = "No content available. Using title only."
|
384 |
+
|
385 |
+
article_info.append(
|
386 |
+
f"ARTICLE {article_number} ({category.upper()}):\n"
|
387 |
+
f"Title: {full_article['title']}\n"
|
388 |
+
f"Source: {source_name}\n"
|
389 |
+
f"URL: {full_article['url']}\n"
|
390 |
+
f"Date: {published_date}\n"
|
391 |
+
f"Content: {description}\n\n"
|
392 |
+
)
|
393 |
+
article_number += 1
|
394 |
+
break
|
395 |
+
else:
|
396 |
+
# If we didn't find the full article, use what we have from the ranked article
|
397 |
+
article_info.append(
|
398 |
+
f"ARTICLE {article_number} ({category.upper()}):\n"
|
399 |
+
f"Title: {article['title']}\n"
|
400 |
+
f"Source: {article['source']}\n"
|
401 |
+
f"URL: {article['url']}\n"
|
402 |
+
f"Date: {article['published_at']}\n"
|
403 |
+
f"Content: No detailed content available.\n\n"
|
404 |
+
)
|
405 |
+
article_number += 1
|
406 |
+
|
407 |
+
# Prepare input for Gemini
|
408 |
+
input_text = f"""USER QUERY: {query}
|
409 |
+
|
410 |
+
IMPORTANT INSTRUCTIONS:
|
411 |
+
- Group sources by their categories with clear headings (e.g., UNBIASED SOURCES, CATEGORY 1 SOURCES)
|
412 |
+
- List UP TO 5 URLs under EACH category heading
|
413 |
+
- Each category should have its OWN numbering from 1-5 (restart at 1 for each category)
|
414 |
+
- Show up to 5 sources PER CATEGORY (not 5 total)
|
415 |
+
- Format each source as: #1. source.com (date) - URL: https://source.com/article
|
416 |
+
|
417 |
+
{''.join(article_info)}"""
|
418 |
+
|
419 |
+
try:
|
420 |
+
# Initialize Gemini client and generate summary
|
421 |
+
model = genai.GenerativeModel(model_name)
|
422 |
+
|
423 |
+
generation_config = genai.GenerationConfig(
|
424 |
+
temperature=0.2, # Moderate temperature for factual but natural summary
|
425 |
+
)
|
426 |
+
|
427 |
+
# Get additional instructions to format the output with clear source categorization
|
428 |
+
post_processing_instructions = """
|
429 |
+
Please ensure your final output follows these formatting guidelines:
|
430 |
+
1. SUMMARY section should be at the top
|
431 |
+
2. KEY FACTS section should follow the summary
|
432 |
+
3. DIFFERENT PERSPECTIVES section should be after key facts
|
433 |
+
4. SOURCES BY CATEGORY section should:
|
434 |
+
- Group sources by their categories (e.g., UNBIASED SOURCES, CATEGORY 1 SOURCES, etc.)
|
435 |
+
- List each category as a separate heading in ALL CAPS
|
436 |
+
- Show UP TO 5 URLs clearly under each category heading
|
437 |
+
- Each category should have its OWN numbering from 1-5 (restart at 1 for each category)
|
438 |
+
- Show the MOST RELEVANT sources from each category
|
439 |
+
"""
|
440 |
+
|
441 |
+
# Prepare the prompt with formatting instructions
|
442 |
+
prompt_with_formatting = f"{summarization_prompt}\n\n{post_processing_instructions}\n\n{input_text}"
|
443 |
+
|
444 |
+
response = model.generate_content(
|
445 |
+
prompt_with_formatting,
|
446 |
+
generation_config=generation_config
|
447 |
+
)
|
448 |
+
|
449 |
+
# Format the response with consistent styling similar to the input query display in UI
|
450 |
+
summary_text = response.text
|
451 |
+
|
452 |
+
# Create formatted summary with consistent styling
|
453 |
+
formatted_summary = f"""
|
454 |
+
MULTI-PERSPECTIVE FACTUAL SUMMARY:
|
455 |
+
|
456 |
+
{summary_text}
|
457 |
+
|
458 |
+
ANALYSIS REASONING:
|
459 |
+
|
460 |
+
{reasoning}
|
461 |
+
|
462 |
+
"""
|
463 |
+
|
464 |
+
# Return the formatted summary
|
465 |
+
return formatted_summary
|
466 |
+
|
467 |
+
except Exception as e:
|
468 |
+
error_msg = f"⚠️ Error generating summary: {e}"
|
469 |
+
print(error_msg)
|
470 |
+
return error_msg
|
check_models.py
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Check available models in Google Generative AI.
|
3 |
+
"""
|
4 |
+
|
5 |
+
import os
|
6 |
+
import google.generativeai as genai
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
|
9 |
+
# Load environment variables
|
10 |
+
load_dotenv()
|
11 |
+
|
12 |
+
# Set up the Gemini API with the API key
|
13 |
+
GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')
|
14 |
+
if not GEMINI_API_KEY:
|
15 |
+
raise ValueError("GEMINI_API_KEY environment variable not set")
|
16 |
+
|
17 |
+
genai.configure(api_key=GEMINI_API_KEY)
|
18 |
+
|
19 |
+
# List available models
|
20 |
+
for model in genai.list_models():
|
21 |
+
print(model.name)
|
gdelt_api.py
ADDED
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
gdelt_api.py - Module for interacting with the GDELT API
|
3 |
+
|
4 |
+
This module provides functions for fetching articles from GDELT
|
5 |
+
and filtering by trusted domains.
|
6 |
+
"""
|
7 |
+
|
8 |
+
import os
|
9 |
+
import requests
|
10 |
+
import datetime
|
11 |
+
from urllib.parse import urlparse
|
12 |
+
from dotenv import load_dotenv
|
13 |
+
|
14 |
+
# Import the whitelist from the separate module
|
15 |
+
from whitelisted_domains import WHITELISTED_DOMAINS
|
16 |
+
|
17 |
+
|
18 |
+
def fetch_articles_from_gdelt(query):
|
19 |
+
"""
|
20 |
+
Fetch news articles from GDELT API.
|
21 |
+
|
22 |
+
Args:
|
23 |
+
query (str): The news query to search for (can be structured GDELT query)
|
24 |
+
|
25 |
+
Returns:
|
26 |
+
list: List of article dictionaries, or empty list if no results or error
|
27 |
+
"""
|
28 |
+
try:
|
29 |
+
# Construct the API URL with the query
|
30 |
+
base_url = "https://api.gdeltproject.org/api/v2/doc/doc"
|
31 |
+
|
32 |
+
# Parse the query to separate main query from other parameters
|
33 |
+
params = {
|
34 |
+
'format': 'json',
|
35 |
+
'maxrecords': int(os.getenv('MAX_ARTICLES_PER_QUERY', 250))
|
36 |
+
}
|
37 |
+
|
38 |
+
# If the query has multiple parameters (separated by &)
|
39 |
+
if '&' in query:
|
40 |
+
parts = query.split('&')
|
41 |
+
for part in parts:
|
42 |
+
if '=' in part:
|
43 |
+
key, value = part.split('=', 1)
|
44 |
+
params[key] = value
|
45 |
+
elif part.startswith('query='):
|
46 |
+
params['query'] = part[6:] # Remove 'query='
|
47 |
+
else:
|
48 |
+
# If it's just a query without the query= prefix
|
49 |
+
if 'query' not in params:
|
50 |
+
params['query'] = part
|
51 |
+
else:
|
52 |
+
# It's just a simple query
|
53 |
+
if query.startswith('query='):
|
54 |
+
params['query'] = query[6:] # Remove 'query='
|
55 |
+
else:
|
56 |
+
params['query'] = query
|
57 |
+
|
58 |
+
# Convert params to query string
|
59 |
+
query_string = "&".join([f"{k}={requests.utils.quote(str(v))}" for k, v in params.items()])
|
60 |
+
url = f"{base_url}?{query_string}"
|
61 |
+
|
62 |
+
print(f"DEBUG - Requesting URL: {url}")
|
63 |
+
|
64 |
+
# Make the request
|
65 |
+
response = requests.get(url, timeout=30)
|
66 |
+
|
67 |
+
# Check if the request was successful
|
68 |
+
if response.status_code == 200:
|
69 |
+
data = response.json()
|
70 |
+
|
71 |
+
# GDELT API returns articles in the 'articles' field
|
72 |
+
if 'articles' in data:
|
73 |
+
return data['articles']
|
74 |
+
|
75 |
+
return []
|
76 |
+
|
77 |
+
except Exception as e:
|
78 |
+
print(f"⚠️ Error fetching articles from GDELT: {e}")
|
79 |
+
return []
|
80 |
+
|
81 |
+
|
82 |
+
def is_whitelisted_domain(url):
|
83 |
+
"""
|
84 |
+
Check if the URL belongs to a whitelisted domain.
|
85 |
+
|
86 |
+
Args:
|
87 |
+
url (str): The URL to check
|
88 |
+
|
89 |
+
Returns:
|
90 |
+
bool: True if the domain is whitelisted, False otherwise
|
91 |
+
"""
|
92 |
+
try:
|
93 |
+
domain = urlparse(url).netloc
|
94 |
+
|
95 |
+
# Handle subdomains by checking if any whitelisted domain is a suffix
|
96 |
+
return any(domain.endswith(trusted_domain) for trusted_domain in WHITELISTED_DOMAINS)
|
97 |
+
except:
|
98 |
+
return False
|
99 |
+
|
100 |
+
|
101 |
+
def format_timestamp(timestamp_str):
|
102 |
+
"""
|
103 |
+
Format a GDELT timestamp string to a more user-friendly format.
|
104 |
+
|
105 |
+
Args:
|
106 |
+
timestamp_str (str): Timestamp string in GDELT format (e.g., "20250829T173000Z")
|
107 |
+
|
108 |
+
Returns:
|
109 |
+
str: Formatted timestamp (e.g., "Aug 29, 2025 17:30")
|
110 |
+
"""
|
111 |
+
if not timestamp_str:
|
112 |
+
return ""
|
113 |
+
|
114 |
+
try:
|
115 |
+
# Handle common GDELT timestamp format
|
116 |
+
if "T" in timestamp_str and len(timestamp_str) >= 15:
|
117 |
+
# Parse YYYYMMDDTHHMMSSZ format
|
118 |
+
year = timestamp_str[0:4]
|
119 |
+
month = timestamp_str[4:6]
|
120 |
+
day = timestamp_str[6:8]
|
121 |
+
hour = timestamp_str[9:11]
|
122 |
+
minute = timestamp_str[11:13]
|
123 |
+
|
124 |
+
# Convert month number to month name
|
125 |
+
month_name = datetime.datetime.strptime(month, "%m").strftime("%b")
|
126 |
+
|
127 |
+
return f"{month_name} {int(day)}, {year} {hour}:{minute}"
|
128 |
+
else:
|
129 |
+
# Return original if not in expected format
|
130 |
+
return timestamp_str
|
131 |
+
except Exception as e:
|
132 |
+
print(f"Error formatting timestamp {timestamp_str}: {e}")
|
133 |
+
return timestamp_str
|
134 |
+
|
135 |
+
|
136 |
+
def filter_by_whitelisted_domains(articles):
|
137 |
+
"""
|
138 |
+
Filter articles to only include those from trusted domains.
|
139 |
+
|
140 |
+
Args:
|
141 |
+
articles (list): List of article dictionaries
|
142 |
+
|
143 |
+
Returns:
|
144 |
+
list: Filtered list of article dictionaries
|
145 |
+
"""
|
146 |
+
if not articles:
|
147 |
+
return []
|
148 |
+
|
149 |
+
trusted_articles = []
|
150 |
+
total_articles = len(articles)
|
151 |
+
|
152 |
+
for article in articles:
|
153 |
+
if 'url' in article and is_whitelisted_domain(article['url']):
|
154 |
+
trusted_articles.append(article)
|
155 |
+
|
156 |
+
filtered_count = total_articles - len(trusted_articles)
|
157 |
+
print(f"Domain filtering: {filtered_count} non-whitelisted articles removed, {len(trusted_articles)} whitelisted articles kept")
|
158 |
+
|
159 |
+
return trusted_articles
|
160 |
+
|
161 |
+
|
162 |
+
def normalize_gdelt_articles(articles):
|
163 |
+
"""
|
164 |
+
Normalize GDELT article format to match the expected format in the rest of the application.
|
165 |
+
|
166 |
+
Args:
|
167 |
+
articles (list): List of GDELT article dictionaries
|
168 |
+
|
169 |
+
Returns:
|
170 |
+
list: List of normalized article dictionaries
|
171 |
+
"""
|
172 |
+
normalized_articles = []
|
173 |
+
|
174 |
+
for article in articles:
|
175 |
+
# Extract domain from URL for source name if not available
|
176 |
+
source_name = article.get('sourcename', '')
|
177 |
+
if not source_name and 'url' in article:
|
178 |
+
domain = urlparse(article['url']).netloc
|
179 |
+
source_name = domain.replace('www.', '')
|
180 |
+
|
181 |
+
# Format the timestamp for better readability
|
182 |
+
raw_timestamp = article.get('seendate', '')
|
183 |
+
formatted_timestamp = format_timestamp(raw_timestamp)
|
184 |
+
|
185 |
+
normalized_articles.append({
|
186 |
+
'title': article.get('title', ''),
|
187 |
+
'description': article.get('seentext', ''),
|
188 |
+
'url': article.get('url', ''),
|
189 |
+
'publishedAt': formatted_timestamp,
|
190 |
+
'source': {'name': source_name}
|
191 |
+
})
|
192 |
+
|
193 |
+
return normalized_articles
|
gdelt_query_builder.py
ADDED
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
gdelt_query_builder.py - Module for building GDELT queries using Gemini API
|
3 |
+
|
4 |
+
This module uses Google's Gemini API to transform natural language queries
|
5 |
+
into structured GDELT query format.
|
6 |
+
"""
|
7 |
+
|
8 |
+
import os
|
9 |
+
import google.generativeai as genai
|
10 |
+
from dotenv import load_dotenv
|
11 |
+
|
12 |
+
# Load environment variables
|
13 |
+
load_dotenv()
|
14 |
+
|
15 |
+
# Set up the Gemini API with the API key from environment variables
|
16 |
+
GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')
|
17 |
+
if not GEMINI_API_KEY:
|
18 |
+
raise ValueError("GEMINI_API_KEY environment variable not set. Please add it to your .env file.")
|
19 |
+
|
20 |
+
# Initialize the Gemini client
|
21 |
+
genai.configure(api_key=GEMINI_API_KEY)
|
22 |
+
|
23 |
+
# Get the model name from environment variables or use default
|
24 |
+
GEMINI_MODEL = os.getenv('GEMINI_MODEL', 'gemini-2.5-flash')
|
25 |
+
|
26 |
+
# Define the system prompt for Gemini
|
27 |
+
SYSTEM_PROMPT = """
|
28 |
+
You are a query builder for the GDELT 2.0 DOC API.
|
29 |
+
Your task is to take the user's natural language request and produce TEN different variations of the query for the GDELT API. Use simple English words to create variations.
|
30 |
+
|
31 |
+
IMPORTANT: First, check if the query contains inappropriate content such as:
|
32 |
+
- Pornography or sexually explicit content
|
33 |
+
|
34 |
+
|
35 |
+
If the query contains ANY of the above, respond EXACTLY with the string:
|
36 |
+
"INAPPROPRIATE_QUERY_DETECTED"
|
37 |
+
|
38 |
+
Otherwise, proceed with the following rules:
|
39 |
+
|
40 |
+
Rules:
|
41 |
+
1. IMPORTANT: Always keep the query variations in the SAME LANGUAGE as the user's original query.
|
42 |
+
2. Correct spelling mistakes in the user input before processing.
|
43 |
+
3. Remove all words with length less than or equal to 2 characters ONLY if they don't affect meaning.
|
44 |
+
4. ONLY use AND operators between terms. DO NOT use OR or NOT operators.
|
45 |
+
5. Create TWO TYPES of variations:
|
46 |
+
a. For the first 5 variations: Create journalistic style queries with verbs and complete phrases (e.g., "announced sanctions", "threatens military action")
|
47 |
+
b. For the last 5 variations: Focus ONLY on organizations, entities, and relevant nouns WITHOUT any verbs or phrases of speech (e.g., "European Union" AND "Russia" AND "sanctions")
|
48 |
+
6. CRITICAL: All terms in the main query part (between AND operators) MUST have a minimum length of 5 characters. This rule does NOT apply to sourcecountry, sourceregion, and timestamp parameters.
|
49 |
+
- Replace short terms (< 5 chars) with equivalent longer terms (e.g., "UK" → "United Kingdom", "US" → "United States", "EU" → "Europe")
|
50 |
+
- For common short words with no longer alternative, add context words to make them more specific
|
51 |
+
7. Contextual understanding:
|
52 |
+
- ALWAYS analyze the query for implied countries, regions, people, or events that suggest a location
|
53 |
+
- For people (e.g., "Biden", "Putin", "Modi"), infer their associated country
|
54 |
+
- For events (e.g., "Olympics in Paris", "Earthquake in Japan"), extract the location
|
55 |
+
- For organizations (e.g., "EU Parliament", "Kremlin"), map to their appropriate region
|
56 |
+
7. Country and region parameters:
|
57 |
+
- Always include sourcecountry and/or sourceregion when you can infer them from context
|
58 |
+
- If user mentions a country, include it as: sourcecountry=<ISO code>
|
59 |
+
- If user mentions a region, include it as: sourceregion=<region code>
|
60 |
+
- For Europe use: sourceregion=EU (not a country code)
|
61 |
+
- For implicit locations (e.g., "Eiffel Tower" → France), add the appropriate country code
|
62 |
+
8. Time range detection:
|
63 |
+
- Analyze for any time-related terms ("yesterday", "last week", "June 2025")
|
64 |
+
- Include startdatetime and enddatetime for any time reference (format YYYYMMDDHHMMSS)
|
65 |
+
- For relative times like "last week" or "recent", calculate actual date ranges
|
66 |
+
9. Main query construction:
|
67 |
+
- Always format as: query=<search terms>
|
68 |
+
- For exact phrases use double quotes: query="climate change"
|
69 |
+
- Connect concepts with AND ONLY: query="climate change" AND "global warming"
|
70 |
+
- Each query should be well-formed with proper placement of operators and quotes
|
71 |
+
10. Language preservation:
|
72 |
+
- If the user's query is in English, all variations must be in English
|
73 |
+
- If the user's query is in Spanish, all variations must be in Spanish
|
74 |
+
- If the user's query is in French, all variations must be in French
|
75 |
+
- Always maintain the original language of the user's query in all variations
|
76 |
+
11. Output format:
|
77 |
+
- Return EXACTLY ten query variations, separated by ||| (three pipe symbols)
|
78 |
+
- For the first 5 variations, focus on journalistic style with complete phrases
|
79 |
+
- For the last 5 variations, focus ONLY on organizations, entities, locations, and relevant nouns WITHOUT any verbs or phrases of speech
|
80 |
+
- Example for entity-only variations: query="United Nations" AND "climate change" AND "Paris"
|
81 |
+
- Each query should be a complete, valid GDELT query string
|
82 |
+
- Format each query correctly with query= at the beginning and & between parameters
|
83 |
+
- Do not explain your choices, just return the ten queries
|
84 |
+
|
85 |
+
Examples:
|
86 |
+
- Input: "Did a tsunami really happen in Japan yesterday?"
|
87 |
+
Output: query="tsunami" AND "Japan"&sourcecountry=JP&startdatetime=20250903000000&enddatetime=20250903235959 ||| query="natural disaster" AND "Japan"&sourcecountry=JP&startdatetime=20250903000000&enddatetime=20250903235959 ||| query="earthquake" AND "tsunami" AND "Japan"&sourcecountry=JP&startdatetime=20250903000000&enddatetime=20250903235959
|
88 |
+
|
89 |
+
- Input: "Is it true that there was an explosion near the Eiffel Tower?"
|
90 |
+
Output: query="explosion" AND "Eiffel Tower"&sourcecountry=FR&sourceregion=EU ||| query="incident" AND "Eiffel Tower" AND "Paris"&sourcecountry=FR&sourceregion=EU ||| query="security" AND "Eiffel Tower" AND "explosion"&sourcecountry=FR&sourceregion=EU
|
91 |
+
|
92 |
+
- Input: "Did Biden announce new sanctions against Russia last week?"
|
93 |
+
Output: query="Biden" AND "sanctions" AND "Russia"&sourcecountry=US&startdatetime=20250828000000&enddatetime=20250903235959 ||| query="United States" AND "sanctions" AND "Russia"&sourcecountry=US&startdatetime=20250828000000&enddatetime=20250903235959 ||| query="Biden" AND "economic measures" AND "Russia"&sourcecountry=US&startdatetime=20250828000000&enddatetime=20250903235959
|
94 |
+
|
95 |
+
- Input: "¿Hubo una manifestación en Madrid ayer?"
|
96 |
+
Output: query="manifestación" AND "Madrid"&sourcecountry=ES&startdatetime=20250903000000&enddatetime=20250903235959 ||| query="protesta" AND "Madrid"&sourcecountry=ES&startdatetime=20250903000000&enddatetime=20250903235959 ||| query="manifestación" AND "España" AND "Madrid"&sourcecountry=ES&startdatetime=20250903000000&enddatetime=20250903235959
|
97 |
+
|
98 |
+
- Input: "Was a new law on AI passed in UK?"
|
99 |
+
Output: query="legislation" AND "artificial intelligence" AND "United Kingdom"&sourcecountry=GB ||| query="regulation" AND "artificial intelligence" AND "United Kingdom"&sourcecountry=GB ||| query="parliament" AND "artificial intelligence" AND "United Kingdom"&sourcecountry=GB
|
100 |
+
"""
|
101 |
+
|
102 |
+
|
103 |
+
def generate_query(user_input: str) -> list:
|
104 |
+
"""
|
105 |
+
Generate ten structured GDELT queries from natural language input using Gemini API.
|
106 |
+
|
107 |
+
Args:
|
108 |
+
user_input (str): The user's natural language query
|
109 |
+
|
110 |
+
Returns:
|
111 |
+
list: List of ten structured GDELT query variations or a list with a single
|
112 |
+
inappropriate content message if the query contains sensitive topics
|
113 |
+
"""
|
114 |
+
try:
|
115 |
+
# Create the chat with system prompt and user input
|
116 |
+
combined_prompt = f"{SYSTEM_PROMPT}\n\nUser request: {user_input}"
|
117 |
+
|
118 |
+
# Generate content with the specified model
|
119 |
+
model = genai.GenerativeModel(GEMINI_MODEL)
|
120 |
+
|
121 |
+
# Set generation config to disable thinking
|
122 |
+
generation_config = genai.GenerationConfig(
|
123 |
+
temperature=0.3, # Slightly higher temperature for more variation
|
124 |
+
)
|
125 |
+
|
126 |
+
response = model.generate_content(
|
127 |
+
combined_prompt,
|
128 |
+
generation_config=generation_config
|
129 |
+
)
|
130 |
+
|
131 |
+
# Extract the response text
|
132 |
+
response_text = response.text.strip()
|
133 |
+
|
134 |
+
# Check if the model detected inappropriate content
|
135 |
+
if response_text == "INAPPROPRIATE_QUERY_DETECTED":
|
136 |
+
print(f"⚠️ Inappropriate query detected: '{user_input}'")
|
137 |
+
# Return a special marker that will be detected in main.py
|
138 |
+
return ["INAPPROPRIATE_QUERY"]
|
139 |
+
|
140 |
+
# Split the response by the separator
|
141 |
+
query_variations = response_text.split('|||')
|
142 |
+
|
143 |
+
# Clean and format each query variation
|
144 |
+
formatted_queries = []
|
145 |
+
for i, query in enumerate(query_variations):
|
146 |
+
query = query.strip()
|
147 |
+
|
148 |
+
# Ensure query format is correct - add "query=" if needed
|
149 |
+
if not (query.startswith('query=') or
|
150 |
+
'sourcecountry=' in query or
|
151 |
+
'sourceregion=' in query or
|
152 |
+
'startdatetime=' in query):
|
153 |
+
# Add query= prefix if not dealing with just filter parameters
|
154 |
+
query = f'query={query}'
|
155 |
+
|
156 |
+
formatted_queries.append(query)
|
157 |
+
|
158 |
+
# If we don't have exactly 10 queries, duplicate or trim as needed
|
159 |
+
while len(formatted_queries) < 10:
|
160 |
+
formatted_queries.append(formatted_queries[0])
|
161 |
+
|
162 |
+
if len(formatted_queries) > 10:
|
163 |
+
formatted_queries = formatted_queries[:10]
|
164 |
+
|
165 |
+
# Log the transformation only once
|
166 |
+
print(f"Original input: '{user_input}'")
|
167 |
+
for i, query in enumerate(formatted_queries):
|
168 |
+
print(f"Query variation {i+1}: '{query}'")
|
169 |
+
|
170 |
+
return formatted_queries
|
171 |
+
|
172 |
+
except Exception as e:
|
173 |
+
if "429" in str(e):
|
174 |
+
print(f"⚠️ Rate limit exceeded for Gemini API. Using original query. Details: {e}")
|
175 |
+
elif "404" in str(e) and "models" in str(e):
|
176 |
+
print(f"⚠️ Model not found. Please check available models. Details: {e}")
|
177 |
+
elif "400" in str(e) and "API key" in str(e):
|
178 |
+
print(f"⚠️ Invalid API key. Please check your GEMINI_API_KEY in .env file. Details: {e}")
|
179 |
+
else:
|
180 |
+
print(f"⚠️ Error generating structured query: {e}")
|
181 |
+
|
182 |
+
# Format the original query with quotes for GDELT for all ten variations
|
183 |
+
fallback_query = f'query="{user_input}"'
|
184 |
+
return [fallback_query] * 10
|
185 |
+
|
186 |
+
|
187 |
+
if __name__ == "__main__":
|
188 |
+
# Example usage
|
189 |
+
test_queries = [
|
190 |
+
"Latest news about climate change in Europe",
|
191 |
+
"Political developments in Ukraine last week",
|
192 |
+
"Economic impact of recent floods in Asia",
|
193 |
+
"Noticias sobre cambio climático en España" # Spanish query
|
194 |
+
]
|
195 |
+
|
196 |
+
print("\nGDELT Query Builder - Example Usage\n")
|
197 |
+
for query in test_queries:
|
198 |
+
print(f"\nTesting query: {query}")
|
199 |
+
query_variations = generate_query(query)
|
200 |
+
for i, variation in enumerate(query_variations):
|
201 |
+
print(f"Variation {i+1}: {variation}")
|
202 |
+
print("-" * 80)
|
google_search.py
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
import os
|
3 |
+
from dotenv import load_dotenv
|
4 |
+
|
5 |
+
# Load environment variables
|
6 |
+
load_dotenv()
|
7 |
+
|
8 |
+
# Get SerpAPI key from environment variables
|
9 |
+
API_KEY = os.getenv('SERPAPI_KEY')
|
10 |
+
if not API_KEY:
|
11 |
+
raise ValueError("SERPAPI_KEY environment variable not set. Please add it to your .env file.")
|
12 |
+
|
13 |
+
def google_search(query, num_results=25):
|
14 |
+
"""
|
15 |
+
Search Google for articles related to the query using SerpAPI.
|
16 |
+
|
17 |
+
Args:
|
18 |
+
query (str): The search query
|
19 |
+
num_results (int): Number of results to return
|
20 |
+
|
21 |
+
Returns:
|
22 |
+
list: List of article dictionaries with title, snippet, and URL
|
23 |
+
"""
|
24 |
+
url = "https://serpapi.com/search"
|
25 |
+
params = {
|
26 |
+
"engine": "google", # search engine
|
27 |
+
"q": query, # search query
|
28 |
+
"api_key": API_KEY, # your key
|
29 |
+
"num": num_results # number of results
|
30 |
+
}
|
31 |
+
|
32 |
+
try:
|
33 |
+
response = requests.get(url, params=params)
|
34 |
+
response.raise_for_status() # Raise exception for HTTP errors
|
35 |
+
results = response.json()
|
36 |
+
|
37 |
+
output = []
|
38 |
+
for item in results.get("organic_results", []):
|
39 |
+
# Format the results to match GDELT article format for consistency
|
40 |
+
article = {
|
41 |
+
"title": item.get("title", "No title"),
|
42 |
+
"snippet": item.get("snippet", "No description available"),
|
43 |
+
"url": item.get("link", ""),
|
44 |
+
"source": {
|
45 |
+
"name": item.get("source", "Google Search")
|
46 |
+
},
|
47 |
+
"publishedAt": "", # No date information in the API response
|
48 |
+
"origin": "google_search" # Add origin to track source
|
49 |
+
}
|
50 |
+
output.append(article)
|
51 |
+
|
52 |
+
print(f"Found {len(output)} results from Google Search")
|
53 |
+
return output
|
54 |
+
|
55 |
+
except Exception as e:
|
56 |
+
print(f"Error with Google Search API: {e}")
|
57 |
+
return []
|
58 |
+
|
59 |
+
# Example usage
|
60 |
+
if __name__ == "__main__":
|
61 |
+
query = "GM stopped operations in India"
|
62 |
+
search_results = google_search(query)
|
63 |
+
for r in search_results:
|
64 |
+
print(f"{r['title']}\n{r['snippet']}\n{r['link']}\n")
|
main.py
ADDED
@@ -0,0 +1,345 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/home/tom/miniconda3/envs/fake_news_detection/bin/python
|
2 |
+
"""
|
3 |
+
main.py - Server for the Fake News Detection system
|
4 |
+
|
5 |
+
This script creates a Flask server that exposes API endpoints to:
|
6 |
+
1. Take user input (news query) from the UI
|
7 |
+
2. Process the request through the fake news detection pipeline
|
8 |
+
3. Return the results to the UI for display
|
9 |
+
"""
|
10 |
+
|
11 |
+
import os
|
12 |
+
import json
|
13 |
+
import time
|
14 |
+
from dotenv import load_dotenv
|
15 |
+
from flask import Flask, request, jsonify
|
16 |
+
from flask_cors import CORS
|
17 |
+
|
18 |
+
# Import required functions from modules
|
19 |
+
from gdelt_api import (
|
20 |
+
fetch_articles_from_gdelt,
|
21 |
+
filter_by_whitelisted_domains,
|
22 |
+
normalize_gdelt_articles
|
23 |
+
)
|
24 |
+
from ranker import ArticleRanker
|
25 |
+
from gdelt_query_builder import generate_query, GEMINI_MODEL
|
26 |
+
import bias_analyzer
|
27 |
+
from google_search import google_search
|
28 |
+
|
29 |
+
# Global variable for embedding model caching across requests
|
30 |
+
print("Preloading embedding model for faster request processing...")
|
31 |
+
# Preload the embedding model at server startup
|
32 |
+
global_ranker = ArticleRanker()
|
33 |
+
|
34 |
+
|
35 |
+
# The function has been removed since bias category descriptions are provided directly by the Gemini model
|
36 |
+
# and stored in the bias_analysis["descriptions"] dictionary
|
37 |
+
|
38 |
+
|
39 |
+
def format_results(query, ranked_articles):
|
40 |
+
"""
|
41 |
+
Format the ranked results in a structured way for the UI.
|
42 |
+
|
43 |
+
Args:
|
44 |
+
query (str): The original query
|
45 |
+
ranked_articles (list): List of ranked article dictionaries
|
46 |
+
|
47 |
+
Returns:
|
48 |
+
dict: Dictionary with formatted results
|
49 |
+
"""
|
50 |
+
result = {}
|
51 |
+
|
52 |
+
if not ranked_articles:
|
53 |
+
result = {
|
54 |
+
"status": "no_results",
|
55 |
+
"message": "⚠️ No news found. Possibly Fake.",
|
56 |
+
"details": "No reliable sources could verify this information.",
|
57 |
+
"articles": []
|
58 |
+
}
|
59 |
+
else:
|
60 |
+
# Get display configuration from environment variables
|
61 |
+
show_scores = os.getenv('SHOW_SIMILARITY_SCORES', 'true').lower() == 'true'
|
62 |
+
show_date = os.getenv('SHOW_PUBLISH_DATE', 'true').lower() == 'true'
|
63 |
+
show_url = os.getenv('SHOW_URL', 'true').lower() == 'true'
|
64 |
+
|
65 |
+
formatted_articles = []
|
66 |
+
for article in ranked_articles:
|
67 |
+
formatted_article = {
|
68 |
+
"rank": article['rank'],
|
69 |
+
"title": article['title'],
|
70 |
+
"source": article['source']
|
71 |
+
}
|
72 |
+
|
73 |
+
if show_scores:
|
74 |
+
formatted_article["similarity_score"] = round(article['similarity_score'], 4)
|
75 |
+
|
76 |
+
if show_url:
|
77 |
+
formatted_article["url"] = article['url']
|
78 |
+
|
79 |
+
if show_date:
|
80 |
+
formatted_article["published_at"] = article['published_at']
|
81 |
+
|
82 |
+
formatted_articles.append(formatted_article)
|
83 |
+
|
84 |
+
result = {
|
85 |
+
"status": "success",
|
86 |
+
"message": f"✅ Found {len(ranked_articles)} relevant articles for: '{query}'",
|
87 |
+
"articles": formatted_articles,
|
88 |
+
"footer": "If the news matches these reliable sources, it's likely true. If it contradicts them or no sources are found, it might be fake."
|
89 |
+
}
|
90 |
+
|
91 |
+
return result
|
92 |
+
|
93 |
+
|
94 |
+
def remove_duplicates(articles):
|
95 |
+
"""
|
96 |
+
Remove duplicate articles based on URL.
|
97 |
+
|
98 |
+
Args:
|
99 |
+
articles (list): List of article dictionaries
|
100 |
+
|
101 |
+
Returns:
|
102 |
+
list: List with duplicate articles removed
|
103 |
+
"""
|
104 |
+
unique_urls = set()
|
105 |
+
unique_articles = []
|
106 |
+
|
107 |
+
for article in articles:
|
108 |
+
if article['url'] not in unique_urls:
|
109 |
+
unique_urls.add(article['url'])
|
110 |
+
unique_articles.append(article)
|
111 |
+
|
112 |
+
return unique_articles
|
113 |
+
|
114 |
+
|
115 |
+
# This function has been removed since Gemini is a cloud API service
|
116 |
+
# that does not require local caching - models are instantiated as needed
|
117 |
+
|
118 |
+
|
119 |
+
def main():
|
120 |
+
"""Main function to run the fake news detection pipeline as a server."""
|
121 |
+
# Load environment variables
|
122 |
+
load_dotenv()
|
123 |
+
|
124 |
+
# Create Flask app
|
125 |
+
app = Flask(__name__, static_folder='static')
|
126 |
+
CORS(app) # Enable CORS for all routes
|
127 |
+
|
128 |
+
@app.route('/static/')
|
129 |
+
def index():
|
130 |
+
"""Serve the main page."""
|
131 |
+
return app.send_static_file('front.html')
|
132 |
+
|
133 |
+
|
134 |
+
@app.route('/api/detect', methods=['POST'])
|
135 |
+
def detect_fake_news():
|
136 |
+
"""API endpoint to check if news is potentially fake."""
|
137 |
+
# Start timing the request processing
|
138 |
+
start_time = time.time()
|
139 |
+
|
140 |
+
data = request.json
|
141 |
+
query = data.get('query', '')
|
142 |
+
|
143 |
+
if not query:
|
144 |
+
return jsonify({
|
145 |
+
"status": "error",
|
146 |
+
"message": "Please provide a news statement to verify."
|
147 |
+
})
|
148 |
+
|
149 |
+
# =====================================================
|
150 |
+
# 1. Input Handling
|
151 |
+
# =====================================================
|
152 |
+
# Generate three variations of the query using Gemini
|
153 |
+
query_variations = generate_query(query)
|
154 |
+
|
155 |
+
# Check if the query was flagged as inappropriate
|
156 |
+
if query_variations == ["INAPPROPRIATE_QUERY"]:
|
157 |
+
return jsonify({
|
158 |
+
"status": "error",
|
159 |
+
"message": "I cannot provide information on this topic as it appears to contain sensitive or inappropriate content."
|
160 |
+
})
|
161 |
+
|
162 |
+
# =====================================================
|
163 |
+
# 2. Data Fetching
|
164 |
+
# =====================================================
|
165 |
+
# Fetch articles from GDELT API for each query variation
|
166 |
+
all_articles = []
|
167 |
+
|
168 |
+
# First, fetch Google search results using the original query
|
169 |
+
# print(f"Fetching Google search results for: {query}")
|
170 |
+
# google_results = google_search(query, num_results=25)
|
171 |
+
# if google_results:
|
172 |
+
# all_articles.extend(google_results)
|
173 |
+
# print(f"Added {len(google_results)} Google search results to articles")
|
174 |
+
|
175 |
+
# Then fetch GDELT results for each query variation
|
176 |
+
for query_var in query_variations:
|
177 |
+
articles = fetch_articles_from_gdelt(query_var)
|
178 |
+
if articles:
|
179 |
+
all_articles.extend(articles)
|
180 |
+
|
181 |
+
# After the loop, check if any articles were found
|
182 |
+
if not all_articles:
|
183 |
+
return jsonify({
|
184 |
+
"status": "no_results",
|
185 |
+
"message": "No articles found on this topic.",
|
186 |
+
"details": "No reliable sources could be found covering this information.",
|
187 |
+
"articles": []
|
188 |
+
})
|
189 |
+
|
190 |
+
# Store unique articles in a set to ensure uniqueness
|
191 |
+
unique_articles = remove_duplicates(all_articles)
|
192 |
+
|
193 |
+
# Apply domain whitelist filtering if enabled in .env
|
194 |
+
use_whitelist_only = os.getenv('USE_WHITELIST_ONLY', 'false').lower() == 'true'
|
195 |
+
if use_whitelist_only:
|
196 |
+
print(f"Filtering articles to only include whitelisted domains...")
|
197 |
+
unique_articles = filter_by_whitelisted_domains(unique_articles)
|
198 |
+
print(f"After whitelist filtering: {len(unique_articles)} articles remain")
|
199 |
+
|
200 |
+
# Normalize the articles to a standard format
|
201 |
+
normalized_articles = normalize_gdelt_articles(unique_articles)
|
202 |
+
|
203 |
+
if not normalized_articles:
|
204 |
+
return jsonify(format_results(query, []))
|
205 |
+
|
206 |
+
# =====================================================
|
207 |
+
# 3. Embedding & Ranking
|
208 |
+
# =====================================================
|
209 |
+
# Initialize the ranker with model from environment variable
|
210 |
+
model_name = os.getenv('SIMILARITY_MODEL', 'intfloat/multilingual-e5-base')
|
211 |
+
|
212 |
+
# Use global ranker if it matches the requested model, otherwise create a new instance
|
213 |
+
if global_ranker.model_name == model_name:
|
214 |
+
ranker = global_ranker
|
215 |
+
else:
|
216 |
+
ranker = ArticleRanker(model_name)
|
217 |
+
|
218 |
+
# Get TOP_K_ARTICLES from .env file
|
219 |
+
TOP_K_ARTICLES = int(os.getenv('TOP_K_ARTICLES', 250))
|
220 |
+
min_threshold = float(os.getenv('MIN_SIMILARITY_THRESHOLD', 0.1))
|
221 |
+
|
222 |
+
# Prepare article texts for embedding
|
223 |
+
article_texts = [f"{article['title']} {article['description'] or ''}" for article in normalized_articles]
|
224 |
+
|
225 |
+
# Create embeddings and calculate similarities
|
226 |
+
query_embedding, article_embeddings = ranker.create_embeddings(query, article_texts)
|
227 |
+
similarities = ranker.calculate_similarities(query_embedding, article_embeddings)
|
228 |
+
|
229 |
+
# Get top articles based on similarity
|
230 |
+
top_indices = ranker.get_top_articles(similarities, normalized_articles, TOP_K_ARTICLES, min_threshold)
|
231 |
+
top_articles = ranker.format_results(top_indices, similarities, normalized_articles)
|
232 |
+
|
233 |
+
# =====================================================
|
234 |
+
# 4. Bias Categorization
|
235 |
+
# =====================================================
|
236 |
+
# Extract outlet names from the TOP_K_ARTICLES
|
237 |
+
# In top_articles, the source is already extracted as a string
|
238 |
+
outlet_names = [article['source'] for article in top_articles]
|
239 |
+
unique_outlets = list(set(outlet_names))
|
240 |
+
print(f"Analyzing {len(unique_outlets)} unique news outlets for bias...")
|
241 |
+
|
242 |
+
# Analyze bias using Gemini - send just the outlet names, not the whole articles
|
243 |
+
bias_analysis = bias_analyzer.analyze_bias(query, unique_outlets, GEMINI_MODEL)
|
244 |
+
|
245 |
+
# =====================================================
|
246 |
+
# 5. Category Embeddings
|
247 |
+
# =====================================================
|
248 |
+
print("\n" + "=" * 80)
|
249 |
+
print("EMBEDDING VECTORS BY BIAS CATEGORY")
|
250 |
+
print("=" * 80)
|
251 |
+
|
252 |
+
# Create embedding vectors for each bias category
|
253 |
+
# 1. Group articles based on their outlet's bias category
|
254 |
+
# 2. Create an embedding vector for each category using ONLY article titles
|
255 |
+
# 3. Rank articles within each category by similarity to query
|
256 |
+
category_rankings = bias_analyzer.categorize_and_rank_by_bias(
|
257 |
+
query, normalized_articles, bias_analysis, ranker, min_threshold
|
258 |
+
)
|
259 |
+
|
260 |
+
# =====================================================
|
261 |
+
# 6. Top N Selection per Category
|
262 |
+
# =====================================================
|
263 |
+
# Get TOP_N_PER_CATEGORY from .env file (default: 5)
|
264 |
+
TOP_N_PER_CATEGORY = int(os.getenv('TOP_N_PER_CATEGORY', 5))
|
265 |
+
|
266 |
+
# Get total counts of articles per category before filtering
|
267 |
+
category_article_counts = {
|
268 |
+
category: len(articles)
|
269 |
+
for category, articles in category_rankings.items()
|
270 |
+
if category not in ["descriptions", "reasoning"]
|
271 |
+
}
|
272 |
+
|
273 |
+
# For each bias category, select the top N articles
|
274 |
+
# These are the most relevant articles within each bias perspective
|
275 |
+
filtered_category_rankings = {}
|
276 |
+
for category, articles in category_rankings.items():
|
277 |
+
# Skip non-category keys like "descriptions" or "reasoning"
|
278 |
+
if category in ["descriptions", "reasoning"]:
|
279 |
+
continue
|
280 |
+
|
281 |
+
filtered_category_rankings[category] = articles[:TOP_N_PER_CATEGORY]
|
282 |
+
|
283 |
+
# Only print if there are articles in this category
|
284 |
+
if len(filtered_category_rankings[category]) > 0:
|
285 |
+
print(f"\n===== Top {len(filtered_category_rankings[category])} articles from {category} category =====")
|
286 |
+
|
287 |
+
# Print detailed information about each selected article
|
288 |
+
for i, article in enumerate(filtered_category_rankings[category], 1):
|
289 |
+
print(f"Article #{i}:")
|
290 |
+
print(f" Title: {article['title']}")
|
291 |
+
print(f" Source: {article['source']}")
|
292 |
+
print(f" Similarity Score: {article['similarity_score']:.4f}")
|
293 |
+
print(f" Rank: {article['rank']}")
|
294 |
+
print(f" URL: {article['url']}")
|
295 |
+
print(f" Published: {article['published_at']}")
|
296 |
+
print("-" * 50)
|
297 |
+
|
298 |
+
# =====================================================
|
299 |
+
# 7. Summarization
|
300 |
+
# =====================================================
|
301 |
+
# Generate summary from articles in all categories
|
302 |
+
print("\nGenerating factual summary using top articles from all categories...")
|
303 |
+
|
304 |
+
# Pass the original bias_analysis to include the reasoning in the summary
|
305 |
+
# We need to add the reasoning to filtered_category_rankings since that's what gets passed to generate_summary
|
306 |
+
filtered_category_rankings["reasoning"] = bias_analysis.get("reasoning", "No reasoning provided")
|
307 |
+
|
308 |
+
# Call the bias_analyzer's generate_summary function with articles from all categories
|
309 |
+
summary = bias_analyzer.generate_summary(
|
310 |
+
query,
|
311 |
+
normalized_articles,
|
312 |
+
filtered_category_rankings,
|
313 |
+
GEMINI_MODEL
|
314 |
+
)
|
315 |
+
|
316 |
+
# Print the summary to terminal (already includes its own formatting)
|
317 |
+
print(summary)
|
318 |
+
|
319 |
+
# Prepare response with ONLY the combined summary (reasoning already appended at end)
|
320 |
+
# Removed separate 'reasoning' key to avoid it showing at the top in the UI
|
321 |
+
result = {
|
322 |
+
"summary": summary
|
323 |
+
}
|
324 |
+
|
325 |
+
return jsonify(result)
|
326 |
+
|
327 |
+
@app.route('/api/health', methods=['GET'])
|
328 |
+
def health_check():
|
329 |
+
"""API endpoint to check if the server is running."""
|
330 |
+
return jsonify({
|
331 |
+
"status": "ok",
|
332 |
+
"message": "Fake News Detection API is running"
|
333 |
+
})
|
334 |
+
|
335 |
+
# Get port from environment variable or use default 5000
|
336 |
+
port = int(os.getenv('PORT', 5000))
|
337 |
+
debug = os.getenv('DEBUG', 'false').lower() == 'true'
|
338 |
+
|
339 |
+
print(f"Starting Fake News Detection API server on port {port}...")
|
340 |
+
# Start the Flask server
|
341 |
+
app.run(host='0.0.0.0', port=port, debug=debug)
|
342 |
+
|
343 |
+
|
344 |
+
if __name__ == "__main__":
|
345 |
+
main()
|
misinformationui/.gitignore
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Miscellaneous
|
2 |
+
*.class
|
3 |
+
*.log
|
4 |
+
*.pyc
|
5 |
+
*.swp
|
6 |
+
.DS_Store
|
7 |
+
.atom/
|
8 |
+
.build/
|
9 |
+
.buildlog/
|
10 |
+
.history
|
11 |
+
.svn/
|
12 |
+
.swiftpm/
|
13 |
+
migrate_working_dir/
|
14 |
+
|
15 |
+
# IntelliJ related
|
16 |
+
*.iml
|
17 |
+
*.ipr
|
18 |
+
*.iws
|
19 |
+
.idea/
|
20 |
+
|
21 |
+
# The .vscode folder contains launch configuration and tasks you configure in
|
22 |
+
# VS Code which you may wish to be included in version control, so this line
|
23 |
+
# is commented out by default.
|
24 |
+
#.vscode/
|
25 |
+
|
26 |
+
# Flutter/Dart/Pub related
|
27 |
+
**/doc/api/
|
28 |
+
**/ios/Flutter/.last_build_id
|
29 |
+
.dart_tool/
|
30 |
+
.flutter-plugins
|
31 |
+
.flutter-plugins-dependencies
|
32 |
+
.pub-cache/
|
33 |
+
.pub/
|
34 |
+
/build/
|
35 |
+
|
36 |
+
# Symbolication related
|
37 |
+
app.*.symbols
|
38 |
+
|
39 |
+
# Obfuscation related
|
40 |
+
app.*.map.json
|
41 |
+
|
42 |
+
# Android Studio will place build artifacts here
|
43 |
+
/android/app/debug
|
44 |
+
/android/app/profile
|
45 |
+
/android/app/release
|
misinformationui/.metadata
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# This file tracks properties of this Flutter project.
|
2 |
+
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
3 |
+
#
|
4 |
+
# This file should be version controlled and should not be manually edited.
|
5 |
+
|
6 |
+
version:
|
7 |
+
revision: "fcf2c11572af6f390246c056bc905eca609533a0"
|
8 |
+
channel: "stable"
|
9 |
+
|
10 |
+
project_type: app
|
11 |
+
|
12 |
+
# Tracks metadata for the flutter migrate command
|
13 |
+
migration:
|
14 |
+
platforms:
|
15 |
+
- platform: root
|
16 |
+
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
17 |
+
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
18 |
+
- platform: android
|
19 |
+
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
20 |
+
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
21 |
+
- platform: ios
|
22 |
+
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
23 |
+
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
24 |
+
- platform: linux
|
25 |
+
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
26 |
+
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
27 |
+
- platform: macos
|
28 |
+
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
29 |
+
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
30 |
+
- platform: web
|
31 |
+
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
32 |
+
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
33 |
+
- platform: windows
|
34 |
+
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
35 |
+
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
36 |
+
|
37 |
+
# User provided section
|
38 |
+
|
39 |
+
# List of Local paths (relative to this file) that should be
|
40 |
+
# ignored by the migrate tool.
|
41 |
+
#
|
42 |
+
# Files that are not part of the templates will be ignored by default.
|
43 |
+
unmanaged_files:
|
44 |
+
- 'lib/main.dart'
|
45 |
+
- 'ios/Runner.xcodeproj/project.pbxproj'
|
misinformationui/README.md
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# misinformationui
|
2 |
+
|
3 |
+
A new Flutter project.
|
4 |
+
|
5 |
+
## Getting Started
|
6 |
+
|
7 |
+
This project is a starting point for a Flutter application.
|
8 |
+
|
9 |
+
A few resources to get you started if this is your first Flutter project:
|
10 |
+
|
11 |
+
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
12 |
+
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
13 |
+
|
14 |
+
For help getting started with Flutter development, view the
|
15 |
+
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
16 |
+
samples, guidance on mobile development, and a full API reference.
|
misinformationui/analysis_options.yaml
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# This file configures the analyzer, which statically analyzes Dart code to
|
2 |
+
# check for errors, warnings, and lints.
|
3 |
+
#
|
4 |
+
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
5 |
+
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
6 |
+
# invoked from the command line by running `flutter analyze`.
|
7 |
+
|
8 |
+
# The following line activates a set of recommended lints for Flutter apps,
|
9 |
+
# packages, and plugins designed to encourage good coding practices.
|
10 |
+
include: package:flutter_lints/flutter.yaml
|
11 |
+
|
12 |
+
linter:
|
13 |
+
# The lint rules applied to this project can be customized in the
|
14 |
+
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
15 |
+
# included above or to enable additional rules. A list of all available lints
|
16 |
+
# and their documentation is published at https://dart.dev/lints.
|
17 |
+
#
|
18 |
+
# Instead of disabling a lint rule for the entire project in the
|
19 |
+
# section below, it can also be suppressed for a single line of code
|
20 |
+
# or a specific dart file by using the `// ignore: name_of_lint` and
|
21 |
+
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
22 |
+
# producing the lint.
|
23 |
+
rules:
|
24 |
+
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
25 |
+
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
26 |
+
|
27 |
+
# Additional information about this file can be found at
|
28 |
+
# https://dart.dev/guides/language/analysis-options
|
misinformationui/android/.gitignore
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
gradle-wrapper.jar
|
2 |
+
/.gradle
|
3 |
+
/captures/
|
4 |
+
/gradlew
|
5 |
+
/gradlew.bat
|
6 |
+
/local.properties
|
7 |
+
GeneratedPluginRegistrant.java
|
8 |
+
.cxx/
|
9 |
+
|
10 |
+
# Remember to never publicly share your keystore.
|
11 |
+
# See https://flutter.dev/to/reference-keystore
|
12 |
+
key.properties
|
13 |
+
**/*.keystore
|
14 |
+
**/*.jks
|
misinformationui/android/app/build.gradle.kts
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
plugins {
|
2 |
+
id("com.android.application")
|
3 |
+
id("kotlin-android")
|
4 |
+
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
5 |
+
id("dev.flutter.flutter-gradle-plugin")
|
6 |
+
}
|
7 |
+
|
8 |
+
android {
|
9 |
+
namespace = "com.example.misinformationui"
|
10 |
+
compileSdk = flutter.compileSdkVersion
|
11 |
+
ndkVersion = flutter.ndkVersion
|
12 |
+
|
13 |
+
compileOptions {
|
14 |
+
sourceCompatibility = JavaVersion.VERSION_11
|
15 |
+
targetCompatibility = JavaVersion.VERSION_11
|
16 |
+
}
|
17 |
+
|
18 |
+
kotlinOptions {
|
19 |
+
jvmTarget = JavaVersion.VERSION_11.toString()
|
20 |
+
}
|
21 |
+
|
22 |
+
defaultConfig {
|
23 |
+
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
24 |
+
applicationId = "com.example.misinformationui"
|
25 |
+
// You can update the following values to match your application needs.
|
26 |
+
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
27 |
+
minSdk = flutter.minSdkVersion
|
28 |
+
targetSdk = flutter.targetSdkVersion
|
29 |
+
versionCode = flutter.versionCode
|
30 |
+
versionName = flutter.versionName
|
31 |
+
}
|
32 |
+
|
33 |
+
buildTypes {
|
34 |
+
release {
|
35 |
+
// TODO: Add your own signing config for the release build.
|
36 |
+
// Signing with the debug keys for now, so `flutter run --release` works.
|
37 |
+
signingConfig = signingConfigs.getByName("debug")
|
38 |
+
}
|
39 |
+
}
|
40 |
+
}
|
41 |
+
|
42 |
+
flutter {
|
43 |
+
source = "../.."
|
44 |
+
}
|
misinformationui/android/app/src/debug/AndroidManifest.xml
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
2 |
+
<!-- The INTERNET permission is required for development. Specifically,
|
3 |
+
the Flutter tool needs it to communicate with the running application
|
4 |
+
to allow setting breakpoints, to provide hot reload, etc.
|
5 |
+
-->
|
6 |
+
<uses-permission android:name="android.permission.INTERNET"/>
|
7 |
+
</manifest>
|
misinformationui/android/app/src/main/AndroidManifest.xml
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
2 |
+
<application
|
3 |
+
android:label="misinformationui"
|
4 |
+
android:name="${applicationName}"
|
5 |
+
android:icon="@mipmap/ic_launcher">
|
6 |
+
<activity
|
7 |
+
android:name=".MainActivity"
|
8 |
+
android:exported="true"
|
9 |
+
android:launchMode="singleTop"
|
10 |
+
android:taskAffinity=""
|
11 |
+
android:theme="@style/LaunchTheme"
|
12 |
+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
13 |
+
android:hardwareAccelerated="true"
|
14 |
+
android:windowSoftInputMode="adjustResize">
|
15 |
+
<!-- Specifies an Android theme to apply to this Activity as soon as
|
16 |
+
the Android process has started. This theme is visible to the user
|
17 |
+
while the Flutter UI initializes. After that, this theme continues
|
18 |
+
to determine the Window background behind the Flutter UI. -->
|
19 |
+
<meta-data
|
20 |
+
android:name="io.flutter.embedding.android.NormalTheme"
|
21 |
+
android:resource="@style/NormalTheme"
|
22 |
+
/>
|
23 |
+
<intent-filter>
|
24 |
+
<action android:name="android.intent.action.MAIN"/>
|
25 |
+
<category android:name="android.intent.category.LAUNCHER"/>
|
26 |
+
</intent-filter>
|
27 |
+
</activity>
|
28 |
+
<!-- Don't delete the meta-data below.
|
29 |
+
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
30 |
+
<meta-data
|
31 |
+
android:name="flutterEmbedding"
|
32 |
+
android:value="2" />
|
33 |
+
</application>
|
34 |
+
<!-- Required to query activities that can process text, see:
|
35 |
+
https://developer.android.com/training/package-visibility and
|
36 |
+
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
37 |
+
|
38 |
+
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
39 |
+
<queries>
|
40 |
+
<intent>
|
41 |
+
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
42 |
+
<data android:mimeType="text/plain"/>
|
43 |
+
</intent>
|
44 |
+
</queries>
|
45 |
+
</manifest>
|
misinformationui/android/app/src/main/kotlin/com/example/misinformationui/MainActivity.kt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.example.misinformationui
|
2 |
+
|
3 |
+
import io.flutter.embedding.android.FlutterActivity
|
4 |
+
|
5 |
+
class MainActivity : FlutterActivity()
|
misinformationui/android/app/src/main/res/drawable-v21/launch_background.xml
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="utf-8"?>
|
2 |
+
<!-- Modify this file to customize your launch splash screen -->
|
3 |
+
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
4 |
+
<item android:drawable="?android:colorBackground" />
|
5 |
+
|
6 |
+
<!-- You can insert your own image assets here -->
|
7 |
+
<!-- <item>
|
8 |
+
<bitmap
|
9 |
+
android:gravity="center"
|
10 |
+
android:src="@mipmap/launch_image" />
|
11 |
+
</item> -->
|
12 |
+
</layer-list>
|
misinformationui/android/app/src/main/res/drawable/launch_background.xml
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="utf-8"?>
|
2 |
+
<!-- Modify this file to customize your launch splash screen -->
|
3 |
+
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
4 |
+
<item android:drawable="@android:color/white" />
|
5 |
+
|
6 |
+
<!-- You can insert your own image assets here -->
|
7 |
+
<!-- <item>
|
8 |
+
<bitmap
|
9 |
+
android:gravity="center"
|
10 |
+
android:src="@mipmap/launch_image" />
|
11 |
+
</item> -->
|
12 |
+
</layer-list>
|
misinformationui/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
ADDED
![]() |
Git LFS Details
|
misinformationui/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
ADDED
![]() |
Git LFS Details
|
misinformationui/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
ADDED
![]() |
Git LFS Details
|
misinformationui/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
ADDED
![]() |
Git LFS Details
|
misinformationui/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
ADDED
![]() |
Git LFS Details
|
misinformationui/android/app/src/main/res/values-night/styles.xml
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="utf-8"?>
|
2 |
+
<resources>
|
3 |
+
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
4 |
+
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
5 |
+
<!-- Show a splash screen on the activity. Automatically removed when
|
6 |
+
the Flutter engine draws its first frame -->
|
7 |
+
<item name="android:windowBackground">@drawable/launch_background</item>
|
8 |
+
</style>
|
9 |
+
<!-- Theme applied to the Android Window as soon as the process has started.
|
10 |
+
This theme determines the color of the Android Window while your
|
11 |
+
Flutter UI initializes, as well as behind your Flutter UI while its
|
12 |
+
running.
|
13 |
+
|
14 |
+
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
15 |
+
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
16 |
+
<item name="android:windowBackground">?android:colorBackground</item>
|
17 |
+
</style>
|
18 |
+
</resources>
|
misinformationui/android/app/src/main/res/values/styles.xml
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="utf-8"?>
|
2 |
+
<resources>
|
3 |
+
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
4 |
+
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
5 |
+
<!-- Show a splash screen on the activity. Automatically removed when
|
6 |
+
the Flutter engine draws its first frame -->
|
7 |
+
<item name="android:windowBackground">@drawable/launch_background</item>
|
8 |
+
</style>
|
9 |
+
<!-- Theme applied to the Android Window as soon as the process has started.
|
10 |
+
This theme determines the color of the Android Window while your
|
11 |
+
Flutter UI initializes, as well as behind your Flutter UI while its
|
12 |
+
running.
|
13 |
+
|
14 |
+
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
15 |
+
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
16 |
+
<item name="android:windowBackground">?android:colorBackground</item>
|
17 |
+
</style>
|
18 |
+
</resources>
|
misinformationui/android/app/src/profile/AndroidManifest.xml
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
2 |
+
<!-- The INTERNET permission is required for development. Specifically,
|
3 |
+
the Flutter tool needs it to communicate with the running application
|
4 |
+
to allow setting breakpoints, to provide hot reload, etc.
|
5 |
+
-->
|
6 |
+
<uses-permission android:name="android.permission.INTERNET"/>
|
7 |
+
</manifest>
|
misinformationui/android/build.gradle.kts
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
allprojects {
|
2 |
+
repositories {
|
3 |
+
google()
|
4 |
+
mavenCentral()
|
5 |
+
}
|
6 |
+
}
|
7 |
+
|
8 |
+
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
|
9 |
+
rootProject.layout.buildDirectory.value(newBuildDir)
|
10 |
+
|
11 |
+
subprojects {
|
12 |
+
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
13 |
+
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
14 |
+
}
|
15 |
+
subprojects {
|
16 |
+
project.evaluationDependsOn(":app")
|
17 |
+
}
|
18 |
+
|
19 |
+
tasks.register<Delete>("clean") {
|
20 |
+
delete(rootProject.layout.buildDirectory)
|
21 |
+
}
|
misinformationui/android/gradle.properties
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
2 |
+
android.useAndroidX=true
|
3 |
+
android.enableJetifier=true
|
misinformationui/android/gradle/wrapper/gradle-wrapper.properties
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
distributionBase=GRADLE_USER_HOME
|
2 |
+
distributionPath=wrapper/dists
|
3 |
+
zipStoreBase=GRADLE_USER_HOME
|
4 |
+
zipStorePath=wrapper/dists
|
5 |
+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
misinformationui/android/settings.gradle.kts
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
pluginManagement {
|
2 |
+
val flutterSdkPath = run {
|
3 |
+
val properties = java.util.Properties()
|
4 |
+
file("local.properties").inputStream().use { properties.load(it) }
|
5 |
+
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
6 |
+
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
7 |
+
flutterSdkPath
|
8 |
+
}
|
9 |
+
|
10 |
+
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
11 |
+
|
12 |
+
repositories {
|
13 |
+
google()
|
14 |
+
mavenCentral()
|
15 |
+
gradlePluginPortal()
|
16 |
+
}
|
17 |
+
}
|
18 |
+
|
19 |
+
plugins {
|
20 |
+
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
21 |
+
id("com.android.application") version "8.7.3" apply false
|
22 |
+
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
23 |
+
}
|
24 |
+
|
25 |
+
include(":app")
|
misinformationui/ios/.gitignore
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
**/dgph
|
2 |
+
*.mode1v3
|
3 |
+
*.mode2v3
|
4 |
+
*.moved-aside
|
5 |
+
*.pbxuser
|
6 |
+
*.perspectivev3
|
7 |
+
**/*sync/
|
8 |
+
.sconsign.dblite
|
9 |
+
.tags*
|
10 |
+
**/.vagrant/
|
11 |
+
**/DerivedData/
|
12 |
+
Icon?
|
13 |
+
**/Pods/
|
14 |
+
**/.symlinks/
|
15 |
+
profile
|
16 |
+
xcuserdata
|
17 |
+
**/.generated/
|
18 |
+
Flutter/App.framework
|
19 |
+
Flutter/Flutter.framework
|
20 |
+
Flutter/Flutter.podspec
|
21 |
+
Flutter/Generated.xcconfig
|
22 |
+
Flutter/ephemeral/
|
23 |
+
Flutter/app.flx
|
24 |
+
Flutter/app.zip
|
25 |
+
Flutter/flutter_assets/
|
26 |
+
Flutter/flutter_export_environment.sh
|
27 |
+
ServiceDefinitions.json
|
28 |
+
Runner/GeneratedPluginRegistrant.*
|
29 |
+
|
30 |
+
# Exceptions to above rules.
|
31 |
+
!default.mode1v3
|
32 |
+
!default.mode2v3
|
33 |
+
!default.pbxuser
|
34 |
+
!default.perspectivev3
|
misinformationui/ios/Flutter/AppFrameworkInfo.plist
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3 |
+
<plist version="1.0">
|
4 |
+
<dict>
|
5 |
+
<key>CFBundleDevelopmentRegion</key>
|
6 |
+
<string>en</string>
|
7 |
+
<key>CFBundleExecutable</key>
|
8 |
+
<string>App</string>
|
9 |
+
<key>CFBundleIdentifier</key>
|
10 |
+
<string>io.flutter.flutter.app</string>
|
11 |
+
<key>CFBundleInfoDictionaryVersion</key>
|
12 |
+
<string>6.0</string>
|
13 |
+
<key>CFBundleName</key>
|
14 |
+
<string>App</string>
|
15 |
+
<key>CFBundlePackageType</key>
|
16 |
+
<string>FMWK</string>
|
17 |
+
<key>CFBundleShortVersionString</key>
|
18 |
+
<string>1.0</string>
|
19 |
+
<key>CFBundleSignature</key>
|
20 |
+
<string>????</string>
|
21 |
+
<key>CFBundleVersion</key>
|
22 |
+
<string>1.0</string>
|
23 |
+
<key>MinimumOSVersion</key>
|
24 |
+
<string>12.0</string>
|
25 |
+
</dict>
|
26 |
+
</plist>
|
misinformationui/ios/Flutter/Debug.xcconfig
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
#include "Generated.xcconfig"
|
misinformationui/ios/Flutter/Release.xcconfig
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
#include "Generated.xcconfig"
|
misinformationui/ios/Runner.xcodeproj/project.pbxproj
ADDED
@@ -0,0 +1,616 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// !$*UTF8*$!
|
2 |
+
{
|
3 |
+
archiveVersion = 1;
|
4 |
+
classes = {
|
5 |
+
};
|
6 |
+
objectVersion = 54;
|
7 |
+
objects = {
|
8 |
+
|
9 |
+
/* Begin PBXBuildFile section */
|
10 |
+
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
11 |
+
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
12 |
+
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
13 |
+
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
14 |
+
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
15 |
+
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
16 |
+
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
17 |
+
/* End PBXBuildFile section */
|
18 |
+
|
19 |
+
/* Begin PBXContainerItemProxy section */
|
20 |
+
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
21 |
+
isa = PBXContainerItemProxy;
|
22 |
+
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
23 |
+
proxyType = 1;
|
24 |
+
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
25 |
+
remoteInfo = Runner;
|
26 |
+
};
|
27 |
+
/* End PBXContainerItemProxy section */
|
28 |
+
|
29 |
+
/* Begin PBXCopyFilesBuildPhase section */
|
30 |
+
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
31 |
+
isa = PBXCopyFilesBuildPhase;
|
32 |
+
buildActionMask = 2147483647;
|
33 |
+
dstPath = "";
|
34 |
+
dstSubfolderSpec = 10;
|
35 |
+
files = (
|
36 |
+
);
|
37 |
+
name = "Embed Frameworks";
|
38 |
+
runOnlyForDeploymentPostprocessing = 0;
|
39 |
+
};
|
40 |
+
/* End PBXCopyFilesBuildPhase section */
|
41 |
+
|
42 |
+
/* Begin PBXFileReference section */
|
43 |
+
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
44 |
+
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
45 |
+
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
46 |
+
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
47 |
+
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
48 |
+
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
49 |
+
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
50 |
+
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
51 |
+
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
52 |
+
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
53 |
+
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
54 |
+
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
55 |
+
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
56 |
+
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
57 |
+
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
58 |
+
/* End PBXFileReference section */
|
59 |
+
|
60 |
+
/* Begin PBXFrameworksBuildPhase section */
|
61 |
+
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
62 |
+
isa = PBXFrameworksBuildPhase;
|
63 |
+
buildActionMask = 2147483647;
|
64 |
+
files = (
|
65 |
+
);
|
66 |
+
runOnlyForDeploymentPostprocessing = 0;
|
67 |
+
};
|
68 |
+
/* End PBXFrameworksBuildPhase section */
|
69 |
+
|
70 |
+
/* Begin PBXGroup section */
|
71 |
+
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
72 |
+
isa = PBXGroup;
|
73 |
+
children = (
|
74 |
+
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
75 |
+
);
|
76 |
+
path = RunnerTests;
|
77 |
+
sourceTree = "<group>";
|
78 |
+
};
|
79 |
+
9740EEB11CF90186004384FC /* Flutter */ = {
|
80 |
+
isa = PBXGroup;
|
81 |
+
children = (
|
82 |
+
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
83 |
+
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
84 |
+
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
85 |
+
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
86 |
+
);
|
87 |
+
name = Flutter;
|
88 |
+
sourceTree = "<group>";
|
89 |
+
};
|
90 |
+
97C146E51CF9000F007C117D = {
|
91 |
+
isa = PBXGroup;
|
92 |
+
children = (
|
93 |
+
9740EEB11CF90186004384FC /* Flutter */,
|
94 |
+
97C146F01CF9000F007C117D /* Runner */,
|
95 |
+
97C146EF1CF9000F007C117D /* Products */,
|
96 |
+
331C8082294A63A400263BE5 /* RunnerTests */,
|
97 |
+
);
|
98 |
+
sourceTree = "<group>";
|
99 |
+
};
|
100 |
+
97C146EF1CF9000F007C117D /* Products */ = {
|
101 |
+
isa = PBXGroup;
|
102 |
+
children = (
|
103 |
+
97C146EE1CF9000F007C117D /* Runner.app */,
|
104 |
+
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
105 |
+
);
|
106 |
+
name = Products;
|
107 |
+
sourceTree = "<group>";
|
108 |
+
};
|
109 |
+
97C146F01CF9000F007C117D /* Runner */ = {
|
110 |
+
isa = PBXGroup;
|
111 |
+
children = (
|
112 |
+
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
113 |
+
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
114 |
+
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
115 |
+
97C147021CF9000F007C117D /* Info.plist */,
|
116 |
+
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
117 |
+
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
118 |
+
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
119 |
+
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
120 |
+
);
|
121 |
+
path = Runner;
|
122 |
+
sourceTree = "<group>";
|
123 |
+
};
|
124 |
+
/* End PBXGroup section */
|
125 |
+
|
126 |
+
/* Begin PBXNativeTarget section */
|
127 |
+
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
128 |
+
isa = PBXNativeTarget;
|
129 |
+
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
130 |
+
buildPhases = (
|
131 |
+
331C807D294A63A400263BE5 /* Sources */,
|
132 |
+
331C807F294A63A400263BE5 /* Resources */,
|
133 |
+
);
|
134 |
+
buildRules = (
|
135 |
+
);
|
136 |
+
dependencies = (
|
137 |
+
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
138 |
+
);
|
139 |
+
name = RunnerTests;
|
140 |
+
productName = RunnerTests;
|
141 |
+
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
142 |
+
productType = "com.apple.product-type.bundle.unit-test";
|
143 |
+
};
|
144 |
+
97C146ED1CF9000F007C117D /* Runner */ = {
|
145 |
+
isa = PBXNativeTarget;
|
146 |
+
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
147 |
+
buildPhases = (
|
148 |
+
9740EEB61CF901F6004384FC /* Run Script */,
|
149 |
+
97C146EA1CF9000F007C117D /* Sources */,
|
150 |
+
97C146EB1CF9000F007C117D /* Frameworks */,
|
151 |
+
97C146EC1CF9000F007C117D /* Resources */,
|
152 |
+
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
153 |
+
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
154 |
+
);
|
155 |
+
buildRules = (
|
156 |
+
);
|
157 |
+
dependencies = (
|
158 |
+
);
|
159 |
+
name = Runner;
|
160 |
+
productName = Runner;
|
161 |
+
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
162 |
+
productType = "com.apple.product-type.application";
|
163 |
+
};
|
164 |
+
/* End PBXNativeTarget section */
|
165 |
+
|
166 |
+
/* Begin PBXProject section */
|
167 |
+
97C146E61CF9000F007C117D /* Project object */ = {
|
168 |
+
isa = PBXProject;
|
169 |
+
attributes = {
|
170 |
+
BuildIndependentTargetsInParallel = YES;
|
171 |
+
LastUpgradeCheck = 1510;
|
172 |
+
ORGANIZATIONNAME = "";
|
173 |
+
TargetAttributes = {
|
174 |
+
331C8080294A63A400263BE5 = {
|
175 |
+
CreatedOnToolsVersion = 14.0;
|
176 |
+
TestTargetID = 97C146ED1CF9000F007C117D;
|
177 |
+
};
|
178 |
+
97C146ED1CF9000F007C117D = {
|
179 |
+
CreatedOnToolsVersion = 7.3.1;
|
180 |
+
LastSwiftMigration = 1100;
|
181 |
+
};
|
182 |
+
};
|
183 |
+
};
|
184 |
+
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
185 |
+
compatibilityVersion = "Xcode 9.3";
|
186 |
+
developmentRegion = en;
|
187 |
+
hasScannedForEncodings = 0;
|
188 |
+
knownRegions = (
|
189 |
+
en,
|
190 |
+
Base,
|
191 |
+
);
|
192 |
+
mainGroup = 97C146E51CF9000F007C117D;
|
193 |
+
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
194 |
+
projectDirPath = "";
|
195 |
+
projectRoot = "";
|
196 |
+
targets = (
|
197 |
+
97C146ED1CF9000F007C117D /* Runner */,
|
198 |
+
331C8080294A63A400263BE5 /* RunnerTests */,
|
199 |
+
);
|
200 |
+
};
|
201 |
+
/* End PBXProject section */
|
202 |
+
|
203 |
+
/* Begin PBXResourcesBuildPhase section */
|
204 |
+
331C807F294A63A400263BE5 /* Resources */ = {
|
205 |
+
isa = PBXResourcesBuildPhase;
|
206 |
+
buildActionMask = 2147483647;
|
207 |
+
files = (
|
208 |
+
);
|
209 |
+
runOnlyForDeploymentPostprocessing = 0;
|
210 |
+
};
|
211 |
+
97C146EC1CF9000F007C117D /* Resources */ = {
|
212 |
+
isa = PBXResourcesBuildPhase;
|
213 |
+
buildActionMask = 2147483647;
|
214 |
+
files = (
|
215 |
+
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
216 |
+
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
217 |
+
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
218 |
+
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
219 |
+
);
|
220 |
+
runOnlyForDeploymentPostprocessing = 0;
|
221 |
+
};
|
222 |
+
/* End PBXResourcesBuildPhase section */
|
223 |
+
|
224 |
+
/* Begin PBXShellScriptBuildPhase section */
|
225 |
+
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
226 |
+
isa = PBXShellScriptBuildPhase;
|
227 |
+
alwaysOutOfDate = 1;
|
228 |
+
buildActionMask = 2147483647;
|
229 |
+
files = (
|
230 |
+
);
|
231 |
+
inputPaths = (
|
232 |
+
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
233 |
+
);
|
234 |
+
name = "Thin Binary";
|
235 |
+
outputPaths = (
|
236 |
+
);
|
237 |
+
runOnlyForDeploymentPostprocessing = 0;
|
238 |
+
shellPath = /bin/sh;
|
239 |
+
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
240 |
+
};
|
241 |
+
9740EEB61CF901F6004384FC /* Run Script */ = {
|
242 |
+
isa = PBXShellScriptBuildPhase;
|
243 |
+
alwaysOutOfDate = 1;
|
244 |
+
buildActionMask = 2147483647;
|
245 |
+
files = (
|
246 |
+
);
|
247 |
+
inputPaths = (
|
248 |
+
);
|
249 |
+
name = "Run Script";
|
250 |
+
outputPaths = (
|
251 |
+
);
|
252 |
+
runOnlyForDeploymentPostprocessing = 0;
|
253 |
+
shellPath = /bin/sh;
|
254 |
+
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
255 |
+
};
|
256 |
+
/* End PBXShellScriptBuildPhase section */
|
257 |
+
|
258 |
+
/* Begin PBXSourcesBuildPhase section */
|
259 |
+
331C807D294A63A400263BE5 /* Sources */ = {
|
260 |
+
isa = PBXSourcesBuildPhase;
|
261 |
+
buildActionMask = 2147483647;
|
262 |
+
files = (
|
263 |
+
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
264 |
+
);
|
265 |
+
runOnlyForDeploymentPostprocessing = 0;
|
266 |
+
};
|
267 |
+
97C146EA1CF9000F007C117D /* Sources */ = {
|
268 |
+
isa = PBXSourcesBuildPhase;
|
269 |
+
buildActionMask = 2147483647;
|
270 |
+
files = (
|
271 |
+
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
272 |
+
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
273 |
+
);
|
274 |
+
runOnlyForDeploymentPostprocessing = 0;
|
275 |
+
};
|
276 |
+
/* End PBXSourcesBuildPhase section */
|
277 |
+
|
278 |
+
/* Begin PBXTargetDependency section */
|
279 |
+
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
280 |
+
isa = PBXTargetDependency;
|
281 |
+
target = 97C146ED1CF9000F007C117D /* Runner */;
|
282 |
+
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
283 |
+
};
|
284 |
+
/* End PBXTargetDependency section */
|
285 |
+
|
286 |
+
/* Begin PBXVariantGroup section */
|
287 |
+
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
288 |
+
isa = PBXVariantGroup;
|
289 |
+
children = (
|
290 |
+
97C146FB1CF9000F007C117D /* Base */,
|
291 |
+
);
|
292 |
+
name = Main.storyboard;
|
293 |
+
sourceTree = "<group>";
|
294 |
+
};
|
295 |
+
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
296 |
+
isa = PBXVariantGroup;
|
297 |
+
children = (
|
298 |
+
97C147001CF9000F007C117D /* Base */,
|
299 |
+
);
|
300 |
+
name = LaunchScreen.storyboard;
|
301 |
+
sourceTree = "<group>";
|
302 |
+
};
|
303 |
+
/* End PBXVariantGroup section */
|
304 |
+
|
305 |
+
/* Begin XCBuildConfiguration section */
|
306 |
+
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
307 |
+
isa = XCBuildConfiguration;
|
308 |
+
buildSettings = {
|
309 |
+
ALWAYS_SEARCH_USER_PATHS = NO;
|
310 |
+
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
311 |
+
CLANG_ANALYZER_NONNULL = YES;
|
312 |
+
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
313 |
+
CLANG_CXX_LIBRARY = "libc++";
|
314 |
+
CLANG_ENABLE_MODULES = YES;
|
315 |
+
CLANG_ENABLE_OBJC_ARC = YES;
|
316 |
+
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
317 |
+
CLANG_WARN_BOOL_CONVERSION = YES;
|
318 |
+
CLANG_WARN_COMMA = YES;
|
319 |
+
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
320 |
+
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
321 |
+
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
322 |
+
CLANG_WARN_EMPTY_BODY = YES;
|
323 |
+
CLANG_WARN_ENUM_CONVERSION = YES;
|
324 |
+
CLANG_WARN_INFINITE_RECURSION = YES;
|
325 |
+
CLANG_WARN_INT_CONVERSION = YES;
|
326 |
+
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
327 |
+
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
328 |
+
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
329 |
+
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
330 |
+
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
331 |
+
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
332 |
+
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
333 |
+
CLANG_WARN_UNREACHABLE_CODE = YES;
|
334 |
+
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
335 |
+
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
336 |
+
COPY_PHASE_STRIP = NO;
|
337 |
+
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
338 |
+
ENABLE_NS_ASSERTIONS = NO;
|
339 |
+
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
340 |
+
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
341 |
+
GCC_C_LANGUAGE_STANDARD = gnu99;
|
342 |
+
GCC_NO_COMMON_BLOCKS = YES;
|
343 |
+
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
344 |
+
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
345 |
+
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
346 |
+
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
347 |
+
GCC_WARN_UNUSED_FUNCTION = YES;
|
348 |
+
GCC_WARN_UNUSED_VARIABLE = YES;
|
349 |
+
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
350 |
+
MTL_ENABLE_DEBUG_INFO = NO;
|
351 |
+
SDKROOT = iphoneos;
|
352 |
+
SUPPORTED_PLATFORMS = iphoneos;
|
353 |
+
TARGETED_DEVICE_FAMILY = "1,2";
|
354 |
+
VALIDATE_PRODUCT = YES;
|
355 |
+
};
|
356 |
+
name = Profile;
|
357 |
+
};
|
358 |
+
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
359 |
+
isa = XCBuildConfiguration;
|
360 |
+
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
361 |
+
buildSettings = {
|
362 |
+
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
363 |
+
CLANG_ENABLE_MODULES = YES;
|
364 |
+
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
365 |
+
ENABLE_BITCODE = NO;
|
366 |
+
INFOPLIST_FILE = Runner/Info.plist;
|
367 |
+
LD_RUNPATH_SEARCH_PATHS = (
|
368 |
+
"$(inherited)",
|
369 |
+
"@executable_path/Frameworks",
|
370 |
+
);
|
371 |
+
PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui;
|
372 |
+
PRODUCT_NAME = "$(TARGET_NAME)";
|
373 |
+
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
374 |
+
SWIFT_VERSION = 5.0;
|
375 |
+
VERSIONING_SYSTEM = "apple-generic";
|
376 |
+
};
|
377 |
+
name = Profile;
|
378 |
+
};
|
379 |
+
331C8088294A63A400263BE5 /* Debug */ = {
|
380 |
+
isa = XCBuildConfiguration;
|
381 |
+
buildSettings = {
|
382 |
+
BUNDLE_LOADER = "$(TEST_HOST)";
|
383 |
+
CODE_SIGN_STYLE = Automatic;
|
384 |
+
CURRENT_PROJECT_VERSION = 1;
|
385 |
+
GENERATE_INFOPLIST_FILE = YES;
|
386 |
+
MARKETING_VERSION = 1.0;
|
387 |
+
PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui.RunnerTests;
|
388 |
+
PRODUCT_NAME = "$(TARGET_NAME)";
|
389 |
+
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
390 |
+
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
391 |
+
SWIFT_VERSION = 5.0;
|
392 |
+
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
393 |
+
};
|
394 |
+
name = Debug;
|
395 |
+
};
|
396 |
+
331C8089294A63A400263BE5 /* Release */ = {
|
397 |
+
isa = XCBuildConfiguration;
|
398 |
+
buildSettings = {
|
399 |
+
BUNDLE_LOADER = "$(TEST_HOST)";
|
400 |
+
CODE_SIGN_STYLE = Automatic;
|
401 |
+
CURRENT_PROJECT_VERSION = 1;
|
402 |
+
GENERATE_INFOPLIST_FILE = YES;
|
403 |
+
MARKETING_VERSION = 1.0;
|
404 |
+
PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui.RunnerTests;
|
405 |
+
PRODUCT_NAME = "$(TARGET_NAME)";
|
406 |
+
SWIFT_VERSION = 5.0;
|
407 |
+
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
408 |
+
};
|
409 |
+
name = Release;
|
410 |
+
};
|
411 |
+
331C808A294A63A400263BE5 /* Profile */ = {
|
412 |
+
isa = XCBuildConfiguration;
|
413 |
+
buildSettings = {
|
414 |
+
BUNDLE_LOADER = "$(TEST_HOST)";
|
415 |
+
CODE_SIGN_STYLE = Automatic;
|
416 |
+
CURRENT_PROJECT_VERSION = 1;
|
417 |
+
GENERATE_INFOPLIST_FILE = YES;
|
418 |
+
MARKETING_VERSION = 1.0;
|
419 |
+
PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui.RunnerTests;
|
420 |
+
PRODUCT_NAME = "$(TARGET_NAME)";
|
421 |
+
SWIFT_VERSION = 5.0;
|
422 |
+
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
423 |
+
};
|
424 |
+
name = Profile;
|
425 |
+
};
|
426 |
+
97C147031CF9000F007C117D /* Debug */ = {
|
427 |
+
isa = XCBuildConfiguration;
|
428 |
+
buildSettings = {
|
429 |
+
ALWAYS_SEARCH_USER_PATHS = NO;
|
430 |
+
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
431 |
+
CLANG_ANALYZER_NONNULL = YES;
|
432 |
+
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
433 |
+
CLANG_CXX_LIBRARY = "libc++";
|
434 |
+
CLANG_ENABLE_MODULES = YES;
|
435 |
+
CLANG_ENABLE_OBJC_ARC = YES;
|
436 |
+
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
437 |
+
CLANG_WARN_BOOL_CONVERSION = YES;
|
438 |
+
CLANG_WARN_COMMA = YES;
|
439 |
+
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
440 |
+
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
441 |
+
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
442 |
+
CLANG_WARN_EMPTY_BODY = YES;
|
443 |
+
CLANG_WARN_ENUM_CONVERSION = YES;
|
444 |
+
CLANG_WARN_INFINITE_RECURSION = YES;
|
445 |
+
CLANG_WARN_INT_CONVERSION = YES;
|
446 |
+
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
447 |
+
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
448 |
+
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
449 |
+
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
450 |
+
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
451 |
+
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
452 |
+
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
453 |
+
CLANG_WARN_UNREACHABLE_CODE = YES;
|
454 |
+
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
455 |
+
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
456 |
+
COPY_PHASE_STRIP = NO;
|
457 |
+
DEBUG_INFORMATION_FORMAT = dwarf;
|
458 |
+
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
459 |
+
ENABLE_TESTABILITY = YES;
|
460 |
+
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
461 |
+
GCC_C_LANGUAGE_STANDARD = gnu99;
|
462 |
+
GCC_DYNAMIC_NO_PIC = NO;
|
463 |
+
GCC_NO_COMMON_BLOCKS = YES;
|
464 |
+
GCC_OPTIMIZATION_LEVEL = 0;
|
465 |
+
GCC_PREPROCESSOR_DEFINITIONS = (
|
466 |
+
"DEBUG=1",
|
467 |
+
"$(inherited)",
|
468 |
+
);
|
469 |
+
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
470 |
+
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
471 |
+
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
472 |
+
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
473 |
+
GCC_WARN_UNUSED_FUNCTION = YES;
|
474 |
+
GCC_WARN_UNUSED_VARIABLE = YES;
|
475 |
+
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
476 |
+
MTL_ENABLE_DEBUG_INFO = YES;
|
477 |
+
ONLY_ACTIVE_ARCH = YES;
|
478 |
+
SDKROOT = iphoneos;
|
479 |
+
TARGETED_DEVICE_FAMILY = "1,2";
|
480 |
+
};
|
481 |
+
name = Debug;
|
482 |
+
};
|
483 |
+
97C147041CF9000F007C117D /* Release */ = {
|
484 |
+
isa = XCBuildConfiguration;
|
485 |
+
buildSettings = {
|
486 |
+
ALWAYS_SEARCH_USER_PATHS = NO;
|
487 |
+
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
488 |
+
CLANG_ANALYZER_NONNULL = YES;
|
489 |
+
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
490 |
+
CLANG_CXX_LIBRARY = "libc++";
|
491 |
+
CLANG_ENABLE_MODULES = YES;
|
492 |
+
CLANG_ENABLE_OBJC_ARC = YES;
|
493 |
+
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
494 |
+
CLANG_WARN_BOOL_CONVERSION = YES;
|
495 |
+
CLANG_WARN_COMMA = YES;
|
496 |
+
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
497 |
+
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
498 |
+
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
499 |
+
CLANG_WARN_EMPTY_BODY = YES;
|
500 |
+
CLANG_WARN_ENUM_CONVERSION = YES;
|
501 |
+
CLANG_WARN_INFINITE_RECURSION = YES;
|
502 |
+
CLANG_WARN_INT_CONVERSION = YES;
|
503 |
+
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
504 |
+
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
505 |
+
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
506 |
+
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
507 |
+
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
508 |
+
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
509 |
+
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
510 |
+
CLANG_WARN_UNREACHABLE_CODE = YES;
|
511 |
+
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
512 |
+
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
513 |
+
COPY_PHASE_STRIP = NO;
|
514 |
+
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
515 |
+
ENABLE_NS_ASSERTIONS = NO;
|
516 |
+
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
517 |
+
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
518 |
+
GCC_C_LANGUAGE_STANDARD = gnu99;
|
519 |
+
GCC_NO_COMMON_BLOCKS = YES;
|
520 |
+
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
521 |
+
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
522 |
+
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
523 |
+
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
524 |
+
GCC_WARN_UNUSED_FUNCTION = YES;
|
525 |
+
GCC_WARN_UNUSED_VARIABLE = YES;
|
526 |
+
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
527 |
+
MTL_ENABLE_DEBUG_INFO = NO;
|
528 |
+
SDKROOT = iphoneos;
|
529 |
+
SUPPORTED_PLATFORMS = iphoneos;
|
530 |
+
SWIFT_COMPILATION_MODE = wholemodule;
|
531 |
+
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
532 |
+
TARGETED_DEVICE_FAMILY = "1,2";
|
533 |
+
VALIDATE_PRODUCT = YES;
|
534 |
+
};
|
535 |
+
name = Release;
|
536 |
+
};
|
537 |
+
97C147061CF9000F007C117D /* Debug */ = {
|
538 |
+
isa = XCBuildConfiguration;
|
539 |
+
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
540 |
+
buildSettings = {
|
541 |
+
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
542 |
+
CLANG_ENABLE_MODULES = YES;
|
543 |
+
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
544 |
+
ENABLE_BITCODE = NO;
|
545 |
+
INFOPLIST_FILE = Runner/Info.plist;
|
546 |
+
LD_RUNPATH_SEARCH_PATHS = (
|
547 |
+
"$(inherited)",
|
548 |
+
"@executable_path/Frameworks",
|
549 |
+
);
|
550 |
+
PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui;
|
551 |
+
PRODUCT_NAME = "$(TARGET_NAME)";
|
552 |
+
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
553 |
+
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
554 |
+
SWIFT_VERSION = 5.0;
|
555 |
+
VERSIONING_SYSTEM = "apple-generic";
|
556 |
+
};
|
557 |
+
name = Debug;
|
558 |
+
};
|
559 |
+
97C147071CF9000F007C117D /* Release */ = {
|
560 |
+
isa = XCBuildConfiguration;
|
561 |
+
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
562 |
+
buildSettings = {
|
563 |
+
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
564 |
+
CLANG_ENABLE_MODULES = YES;
|
565 |
+
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
566 |
+
ENABLE_BITCODE = NO;
|
567 |
+
INFOPLIST_FILE = Runner/Info.plist;
|
568 |
+
LD_RUNPATH_SEARCH_PATHS = (
|
569 |
+
"$(inherited)",
|
570 |
+
"@executable_path/Frameworks",
|
571 |
+
);
|
572 |
+
PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui;
|
573 |
+
PRODUCT_NAME = "$(TARGET_NAME)";
|
574 |
+
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
575 |
+
SWIFT_VERSION = 5.0;
|
576 |
+
VERSIONING_SYSTEM = "apple-generic";
|
577 |
+
};
|
578 |
+
name = Release;
|
579 |
+
};
|
580 |
+
/* End XCBuildConfiguration section */
|
581 |
+
|
582 |
+
/* Begin XCConfigurationList section */
|
583 |
+
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
584 |
+
isa = XCConfigurationList;
|
585 |
+
buildConfigurations = (
|
586 |
+
331C8088294A63A400263BE5 /* Debug */,
|
587 |
+
331C8089294A63A400263BE5 /* Release */,
|
588 |
+
331C808A294A63A400263BE5 /* Profile */,
|
589 |
+
);
|
590 |
+
defaultConfigurationIsVisible = 0;
|
591 |
+
defaultConfigurationName = Release;
|
592 |
+
};
|
593 |
+
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
594 |
+
isa = XCConfigurationList;
|
595 |
+
buildConfigurations = (
|
596 |
+
97C147031CF9000F007C117D /* Debug */,
|
597 |
+
97C147041CF9000F007C117D /* Release */,
|
598 |
+
249021D3217E4FDB00AE95B9 /* Profile */,
|
599 |
+
);
|
600 |
+
defaultConfigurationIsVisible = 0;
|
601 |
+
defaultConfigurationName = Release;
|
602 |
+
};
|
603 |
+
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
604 |
+
isa = XCConfigurationList;
|
605 |
+
buildConfigurations = (
|
606 |
+
97C147061CF9000F007C117D /* Debug */,
|
607 |
+
97C147071CF9000F007C117D /* Release */,
|
608 |
+
249021D4217E4FDB00AE95B9 /* Profile */,
|
609 |
+
);
|
610 |
+
defaultConfigurationIsVisible = 0;
|
611 |
+
defaultConfigurationName = Release;
|
612 |
+
};
|
613 |
+
/* End XCConfigurationList section */
|
614 |
+
};
|
615 |
+
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
616 |
+
}
|
misinformationui/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<Workspace
|
3 |
+
version = "1.0">
|
4 |
+
<FileRef
|
5 |
+
location = "self:">
|
6 |
+
</FileRef>
|
7 |
+
</Workspace>
|
misinformationui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3 |
+
<plist version="1.0">
|
4 |
+
<dict>
|
5 |
+
<key>IDEDidComputeMac32BitWarning</key>
|
6 |
+
<true/>
|
7 |
+
</dict>
|
8 |
+
</plist>
|
misinformationui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3 |
+
<plist version="1.0">
|
4 |
+
<dict>
|
5 |
+
<key>PreviewsEnabled</key>
|
6 |
+
<false/>
|
7 |
+
</dict>
|
8 |
+
</plist>
|
misinformationui/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
ADDED
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<Scheme
|
3 |
+
LastUpgradeVersion = "1510"
|
4 |
+
version = "1.3">
|
5 |
+
<BuildAction
|
6 |
+
parallelizeBuildables = "YES"
|
7 |
+
buildImplicitDependencies = "YES">
|
8 |
+
<BuildActionEntries>
|
9 |
+
<BuildActionEntry
|
10 |
+
buildForTesting = "YES"
|
11 |
+
buildForRunning = "YES"
|
12 |
+
buildForProfiling = "YES"
|
13 |
+
buildForArchiving = "YES"
|
14 |
+
buildForAnalyzing = "YES">
|
15 |
+
<BuildableReference
|
16 |
+
BuildableIdentifier = "primary"
|
17 |
+
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
18 |
+
BuildableName = "Runner.app"
|
19 |
+
BlueprintName = "Runner"
|
20 |
+
ReferencedContainer = "container:Runner.xcodeproj">
|
21 |
+
</BuildableReference>
|
22 |
+
</BuildActionEntry>
|
23 |
+
</BuildActionEntries>
|
24 |
+
</BuildAction>
|
25 |
+
<TestAction
|
26 |
+
buildConfiguration = "Debug"
|
27 |
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
28 |
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
29 |
+
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
30 |
+
shouldUseLaunchSchemeArgsEnv = "YES">
|
31 |
+
<MacroExpansion>
|
32 |
+
<BuildableReference
|
33 |
+
BuildableIdentifier = "primary"
|
34 |
+
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
35 |
+
BuildableName = "Runner.app"
|
36 |
+
BlueprintName = "Runner"
|
37 |
+
ReferencedContainer = "container:Runner.xcodeproj">
|
38 |
+
</BuildableReference>
|
39 |
+
</MacroExpansion>
|
40 |
+
<Testables>
|
41 |
+
<TestableReference
|
42 |
+
skipped = "NO"
|
43 |
+
parallelizable = "YES">
|
44 |
+
<BuildableReference
|
45 |
+
BuildableIdentifier = "primary"
|
46 |
+
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
47 |
+
BuildableName = "RunnerTests.xctest"
|
48 |
+
BlueprintName = "RunnerTests"
|
49 |
+
ReferencedContainer = "container:Runner.xcodeproj">
|
50 |
+
</BuildableReference>
|
51 |
+
</TestableReference>
|
52 |
+
</Testables>
|
53 |
+
</TestAction>
|
54 |
+
<LaunchAction
|
55 |
+
buildConfiguration = "Debug"
|
56 |
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
57 |
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
58 |
+
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
59 |
+
launchStyle = "0"
|
60 |
+
useCustomWorkingDirectory = "NO"
|
61 |
+
ignoresPersistentStateOnLaunch = "NO"
|
62 |
+
debugDocumentVersioning = "YES"
|
63 |
+
debugServiceExtension = "internal"
|
64 |
+
enableGPUValidationMode = "1"
|
65 |
+
allowLocationSimulation = "YES">
|
66 |
+
<BuildableProductRunnable
|
67 |
+
runnableDebuggingMode = "0">
|
68 |
+
<BuildableReference
|
69 |
+
BuildableIdentifier = "primary"
|
70 |
+
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
71 |
+
BuildableName = "Runner.app"
|
72 |
+
BlueprintName = "Runner"
|
73 |
+
ReferencedContainer = "container:Runner.xcodeproj">
|
74 |
+
</BuildableReference>
|
75 |
+
</BuildableProductRunnable>
|
76 |
+
</LaunchAction>
|
77 |
+
<ProfileAction
|
78 |
+
buildConfiguration = "Profile"
|
79 |
+
shouldUseLaunchSchemeArgsEnv = "YES"
|
80 |
+
savedToolIdentifier = ""
|
81 |
+
useCustomWorkingDirectory = "NO"
|
82 |
+
debugDocumentVersioning = "YES">
|
83 |
+
<BuildableProductRunnable
|
84 |
+
runnableDebuggingMode = "0">
|
85 |
+
<BuildableReference
|
86 |
+
BuildableIdentifier = "primary"
|
87 |
+
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
88 |
+
BuildableName = "Runner.app"
|
89 |
+
BlueprintName = "Runner"
|
90 |
+
ReferencedContainer = "container:Runner.xcodeproj">
|
91 |
+
</BuildableReference>
|
92 |
+
</BuildableProductRunnable>
|
93 |
+
</ProfileAction>
|
94 |
+
<AnalyzeAction
|
95 |
+
buildConfiguration = "Debug">
|
96 |
+
</AnalyzeAction>
|
97 |
+
<ArchiveAction
|
98 |
+
buildConfiguration = "Release"
|
99 |
+
revealArchiveInOrganizer = "YES">
|
100 |
+
</ArchiveAction>
|
101 |
+
</Scheme>
|
misinformationui/ios/Runner.xcworkspace/contents.xcworkspacedata
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<Workspace
|
3 |
+
version = "1.0">
|
4 |
+
<FileRef
|
5 |
+
location = "group:Runner.xcodeproj">
|
6 |
+
</FileRef>
|
7 |
+
</Workspace>
|
misinformationui/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3 |
+
<plist version="1.0">
|
4 |
+
<dict>
|
5 |
+
<key>IDEDidComputeMac32BitWarning</key>
|
6 |
+
<true/>
|
7 |
+
</dict>
|
8 |
+
</plist>
|
misinformationui/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3 |
+
<plist version="1.0">
|
4 |
+
<dict>
|
5 |
+
<key>PreviewsEnabled</key>
|
6 |
+
<false/>
|
7 |
+
</dict>
|
8 |
+
</plist>
|
misinformationui/ios/Runner/AppDelegate.swift
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Flutter
|
2 |
+
import UIKit
|
3 |
+
|
4 |
+
@main
|
5 |
+
@objc class AppDelegate: FlutterAppDelegate {
|
6 |
+
override func application(
|
7 |
+
_ application: UIApplication,
|
8 |
+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
9 |
+
) -> Bool {
|
10 |
+
GeneratedPluginRegistrant.register(with: self)
|
11 |
+
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
12 |
+
}
|
13 |
+
}
|
misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
ADDED
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"images" : [
|
3 |
+
{
|
4 |
+
"size" : "20x20",
|
5 |
+
"idiom" : "iphone",
|
6 |
+
"filename" : "[email protected]",
|
7 |
+
"scale" : "2x"
|
8 |
+
},
|
9 |
+
{
|
10 |
+
"size" : "20x20",
|
11 |
+
"idiom" : "iphone",
|
12 |
+
"filename" : "[email protected]",
|
13 |
+
"scale" : "3x"
|
14 |
+
},
|
15 |
+
{
|
16 |
+
"size" : "29x29",
|
17 |
+
"idiom" : "iphone",
|
18 |
+
"filename" : "[email protected]",
|
19 |
+
"scale" : "1x"
|
20 |
+
},
|
21 |
+
{
|
22 |
+
"size" : "29x29",
|
23 |
+
"idiom" : "iphone",
|
24 |
+
"filename" : "[email protected]",
|
25 |
+
"scale" : "2x"
|
26 |
+
},
|
27 |
+
{
|
28 |
+
"size" : "29x29",
|
29 |
+
"idiom" : "iphone",
|
30 |
+
"filename" : "[email protected]",
|
31 |
+
"scale" : "3x"
|
32 |
+
},
|
33 |
+
{
|
34 |
+
"size" : "40x40",
|
35 |
+
"idiom" : "iphone",
|
36 |
+
"filename" : "[email protected]",
|
37 |
+
"scale" : "2x"
|
38 |
+
},
|
39 |
+
{
|
40 |
+
"size" : "40x40",
|
41 |
+
"idiom" : "iphone",
|
42 |
+
"filename" : "[email protected]",
|
43 |
+
"scale" : "3x"
|
44 |
+
},
|
45 |
+
{
|
46 |
+
"size" : "60x60",
|
47 |
+
"idiom" : "iphone",
|
48 |
+
"filename" : "[email protected]",
|
49 |
+
"scale" : "2x"
|
50 |
+
},
|
51 |
+
{
|
52 |
+
"size" : "60x60",
|
53 |
+
"idiom" : "iphone",
|
54 |
+
"filename" : "[email protected]",
|
55 |
+
"scale" : "3x"
|
56 |
+
},
|
57 |
+
{
|
58 |
+
"size" : "20x20",
|
59 |
+
"idiom" : "ipad",
|
60 |
+
"filename" : "[email protected]",
|
61 |
+
"scale" : "1x"
|
62 |
+
},
|
63 |
+
{
|
64 |
+
"size" : "20x20",
|
65 |
+
"idiom" : "ipad",
|
66 |
+
"filename" : "[email protected]",
|
67 |
+
"scale" : "2x"
|
68 |
+
},
|
69 |
+
{
|
70 |
+
"size" : "29x29",
|
71 |
+
"idiom" : "ipad",
|
72 |
+
"filename" : "[email protected]",
|
73 |
+
"scale" : "1x"
|
74 |
+
},
|
75 |
+
{
|
76 |
+
"size" : "29x29",
|
77 |
+
"idiom" : "ipad",
|
78 |
+
"filename" : "[email protected]",
|
79 |
+
"scale" : "2x"
|
80 |
+
},
|
81 |
+
{
|
82 |
+
"size" : "40x40",
|
83 |
+
"idiom" : "ipad",
|
84 |
+
"filename" : "[email protected]",
|
85 |
+
"scale" : "1x"
|
86 |
+
},
|
87 |
+
{
|
88 |
+
"size" : "40x40",
|
89 |
+
"idiom" : "ipad",
|
90 |
+
"filename" : "[email protected]",
|
91 |
+
"scale" : "2x"
|
92 |
+
},
|
93 |
+
{
|
94 |
+
"size" : "76x76",
|
95 |
+
"idiom" : "ipad",
|
96 |
+
"filename" : "[email protected]",
|
97 |
+
"scale" : "1x"
|
98 |
+
},
|
99 |
+
{
|
100 |
+
"size" : "76x76",
|
101 |
+
"idiom" : "ipad",
|
102 |
+
"filename" : "[email protected]",
|
103 |
+
"scale" : "2x"
|
104 |
+
},
|
105 |
+
{
|
106 |
+
"size" : "83.5x83.5",
|
107 |
+
"idiom" : "ipad",
|
108 |
+
"filename" : "[email protected]",
|
109 |
+
"scale" : "2x"
|
110 |
+
},
|
111 |
+
{
|
112 |
+
"size" : "1024x1024",
|
113 |
+
"idiom" : "ios-marketing",
|
114 |
+
"filename" : "[email protected]",
|
115 |
+
"scale" : "1x"
|
116 |
+
}
|
117 |
+
],
|
118 |
+
"info" : {
|
119 |
+
"version" : 1,
|
120 |
+
"author" : "xcode"
|
121 |
+
}
|
122 |
+
}
|
misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
ADDED
![]() |
Git LFS Details
|
misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
ADDED
![]() |
Git LFS Details
|