tomalex04 commited on
Commit
700863c
·
0 Parent(s):

Initial commit

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env.example +35 -0
  2. .gitattributes +2 -0
  3. .gitignore +40 -0
  4. README.md +73 -0
  5. README_GEMINI.md +46 -0
  6. bias_analyzer.py +470 -0
  7. check_models.py +21 -0
  8. gdelt_api.py +193 -0
  9. gdelt_query_builder.py +202 -0
  10. google_search.py +64 -0
  11. main.py +345 -0
  12. misinformationui/.gitignore +45 -0
  13. misinformationui/.metadata +45 -0
  14. misinformationui/README.md +16 -0
  15. misinformationui/analysis_options.yaml +28 -0
  16. misinformationui/android/.gitignore +14 -0
  17. misinformationui/android/app/build.gradle.kts +44 -0
  18. misinformationui/android/app/src/debug/AndroidManifest.xml +7 -0
  19. misinformationui/android/app/src/main/AndroidManifest.xml +45 -0
  20. misinformationui/android/app/src/main/kotlin/com/example/misinformationui/MainActivity.kt +5 -0
  21. misinformationui/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  22. misinformationui/android/app/src/main/res/drawable/launch_background.xml +12 -0
  23. misinformationui/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +3 -0
  24. misinformationui/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +3 -0
  25. misinformationui/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +3 -0
  26. misinformationui/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +3 -0
  27. misinformationui/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +3 -0
  28. misinformationui/android/app/src/main/res/values-night/styles.xml +18 -0
  29. misinformationui/android/app/src/main/res/values/styles.xml +18 -0
  30. misinformationui/android/app/src/profile/AndroidManifest.xml +7 -0
  31. misinformationui/android/build.gradle.kts +21 -0
  32. misinformationui/android/gradle.properties +3 -0
  33. misinformationui/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  34. misinformationui/android/settings.gradle.kts +25 -0
  35. misinformationui/ios/.gitignore +34 -0
  36. misinformationui/ios/Flutter/AppFrameworkInfo.plist +26 -0
  37. misinformationui/ios/Flutter/Debug.xcconfig +1 -0
  38. misinformationui/ios/Flutter/Release.xcconfig +1 -0
  39. misinformationui/ios/Runner.xcodeproj/project.pbxproj +616 -0
  40. misinformationui/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  41. misinformationui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  42. misinformationui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  43. misinformationui/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
  44. misinformationui/ios/Runner.xcworkspace/contents.xcworkspacedata +7 -0
  45. misinformationui/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  46. misinformationui/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  47. misinformationui/ios/Runner/AppDelegate.swift +13 -0
  48. misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
  49. misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected] +3 -0
  50. 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

  • SHA256: 6a7c8f0d703e3682108f9662f813302236240d3f8f638bb391e32bfb96055fef
  • Pointer size: 128 Bytes
  • Size of remote file: 544 Bytes
misinformationui/android/app/src/main/res/mipmap-mdpi/ic_launcher.png ADDED

Git LFS Details

  • SHA256: c7c0c0189145e4e32a401c61c9bdc615754b0264e7afae24e834bb81049eaf81
  • Pointer size: 128 Bytes
  • Size of remote file: 442 Bytes
misinformationui/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png ADDED

Git LFS Details

  • SHA256: e14aa40904929bf313fded22cf7e7ffcbf1d1aac4263b5ef1be8bfce650397aa
  • Pointer size: 128 Bytes
  • Size of remote file: 721 Bytes
misinformationui/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png ADDED

Git LFS Details

  • SHA256: 4d470bf22d5c17d84edc5f82516d1ba8a1c09559cd761cefb792f86d9f52b540
  • Pointer size: 129 Bytes
  • Size of remote file: 1.03 kB
misinformationui/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png ADDED

Git LFS Details

  • SHA256: 3c34e1f298d0c9ea3455d46db6b7759c8211a49e9ec6e44b635fc5c87dfb4180
  • Pointer size: 129 Bytes
  • Size of remote file: 1.44 kB
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

  • SHA256: 7770183009e914112de7d8ef1d235a6a30c5834424858e0d2f8253f6b8d31926
  • Pointer size: 130 Bytes
  • Size of remote file: 10.9 kB
misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected] ADDED

Git LFS Details

  • SHA256: cab10a0d391ec5bc09ef50ce49e8ad401cee7ef03707ec0923a222c5c2b3d212
  • Pointer size: 128 Bytes
  • Size of remote file: 295 Bytes