diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..66ce5bae74596ec6a1700ba8ce9f2b27cb023467 --- /dev/null +++ b/.env.example @@ -0,0 +1,35 @@ +# Example .env file for Fake News Detection System +# Rename this file to .env and modify values as needed + +# GDELT API configuration +# No API key needed for GDELT +MAX_ARTICLES_PER_QUERY=250 + +# Ranking configuration +TOP_K_ARTICLES=250 +SIMILARITY_MODEL=intfloat/multilingual-e5-base +MIN_SIMILARITY_THRESHOLD=0.1 + +# Display configuration +SHOW_SIMILARITY_SCORES=true +SHOW_PUBLISH_DATE=true +SHOW_URL=true + +# Domain filtering configuration +USE_WHITELIST_ONLY=false + +# Google Gemini API +# Get your API key from https://ai.google.dev/ +GEMINI_API_KEY=your_gemini_api_key_here +GEMINI_MODEL=gemini-2.5-flash + +# Google Search API (SerpAPI) +# Get your API key from https://serpapi.com/ +SERPAPI_KEY=your_serpapi_key_here + +# Bias Analysis configuration +TOP_ARTICLES_PER_BIAS_CATEGORY=5 + +# Server configuration +PORT=5000 +DEBUG=false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..0f585748bd589cc1a0f6c8553f00b976fc2c1da1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.wasm filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a0afa7ab2549ed67b984af9d90789b9c2c3f0752 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Environment variables +.env +.env.* +!.env.example + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +env/ +ENV/ +env.bak/ +venv.bak/ + +# IDE files +.idea/ +.vscode/ +*.swp +*.swo +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..961835ab53b3b51bca23fef5ddf4164eeeed4af2 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# Fake News Detection (GDELT + Gemini) with Minimal UI + +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). + +## Key Features +- Query expansion: 10 GDELT query variations, language-preserving, AND-only operators. +- Sensitive-query guard: pornography/religion and similar sensitive topics short‑circuit with “I cannot respond to this query.” +- GDELT ingestion and normalization. +- Gemini-driven bias analysis with categories; one category is strictly named “unbiased”. +- Per-category ranking using a cached local embedding model (SentenceTransformers), shared across requests. +- Multi‑perspective summarization: + - Sends top URLs from all categories (including unbiased) to Gemini. + - Summary lists sources grouped by category with up to 5 URLs per category. + - Appends the “reasoning” string (from bias analysis) after the sources. +- Optional domain whitelisting (toggle in .env). +- Terminal and UI show the exact same summary string. + +## Requirements +- Python 3.11+ +- Conda/venv recommended +- Packages: flask, flask-cors, python-dotenv, requests, sentence-transformers, torch, google-generativeai + +## Setup +1. Create and activate environment (example with conda): + - conda create -n fake_news_detection python=3.11 -y + - conda activate fake_news_detection +2. Install deps: + - pip install -r requirements.txt (if present) or install the packages listed above. +3. Copy .env.example to .env and set values: + - GEMINI_API_KEY, GEMINI_MODEL (e.g., gemini-1.5-pro or gemini-2.5-pro) + - MAX_ARTICLES_PER_QUERY, TOP_N_PER_CATEGORY, MIN_SIMILARITY_THRESHOLD + - SIMILARITY_MODEL (e.g., intfloat/multilingual-e5-base) + - SHOW_SIMILARITY_SCORES, SHOW_PUBLISH_DATE, SHOW_URL + - USE_WHITELIST_ONLY (true/false) + - PORT, DEBUG + +## Run +- Linux: + - chmod +x ./main.py + - ./main.py +- Visit http://127.0.0.1:5000 + +## API +POST /api/detect +- Body: {"query": "your question"} +- Returns (simplified): +``` +{ + "query": "...", + "summary": "MULTI-PERSPECTIVE FACTUAL SUMMARY...\n\n...SOURCES BY CATEGORY...\n\n...REASONING: ...", + "status": "ok" | "no_results" | "blocked" +} +``` +Notes: +- If the query is sensitive, status=blocked and summary contains: “I cannot respond to this query.” +- Only the summary string is printed to terminal and sent to UI, and the UI renders it verbatim. + +## Behavior Details +- Local embedding model is loaded once and cached for reuse across requests. +- Gemini runs in the cloud (no caching). +- Bias categories come from Gemini; one is enforced/normalized to exactly “unbiased”. +- Summarization uses top URLs from all categories and instructs Gemini to: + - Group sources by category, + - List up to 5 URLs per category (numbering restarts at 1 inside each category), + - Then append the bias-analysis “reasoning” section. + +## Whitelist Filtering +- USE_WHITELIST_ONLY=true limits articles to whitelisted domains. +- When false, all domains are considered. + +## Frontend +- static/ contains a minimal JS client. +- It outputs exactly the summary string received from backend (no hardcoded counts/colors/extra text; no horizontal scrolling). diff --git a/README_GEMINI.md b/README_GEMINI.md new file mode 100644 index 0000000000000000000000000000000000000000..69a8ffaaf1d94985850e674021b8b4b72d752f8c --- /dev/null +++ b/README_GEMINI.md @@ -0,0 +1,46 @@ +# Gemini Integration: GDELT Query Builder & Summarization + +This project uses Google’s Gemini for: +- Building 10 language-preserving GDELT query variations. +- Analyzing outlet bias and returning categories (must include exactly “unbiased”). +- Producing a multi‑perspective factual summary grouped by bias categories. + +Gemini runs in the cloud and is not cached. The local embedding model is cached and shared across requests. + +## Setup +- Get an API key from https://ai.google.dev/ +- .env: + - GEMINI_API_KEY=your_key + - GEMINI_MODEL=gemini-1.5-pro (or gemini-2.5-pro / flash variants) + +## Query Builder (gdelt_query_builder.py) +- Generates EXACTLY 10 variations separated by |||. +- Preserves user language. +- Uses AND-only operators between terms. +- Adds sourcecountry/sourceregion and datetimes when implied. +- Sensitive-query guard: + - 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). + - The backend detects this and immediately returns a summary “I cannot respond to this query.” (status=blocked). + +Example request body to backend: +``` +{"query": "news about war in Ukraine"} +``` + +## Bias Analysis +- Gemini returns bias categories and counts; one category is normalized to exactly “unbiased”. +- Reasoning text explains the categorization logic; this is appended after sources in the final summary. + +## Summarization +- Backend sends top URLs from all categories (including unbiased) to Gemini, labeled by category. +- Gemini instruction highlights: + - Produce a concise factual answer first. + - Then list SOURCES BY CATEGORY with up to 5 URLs per category. + - Numbering restarts at 1 per category (1–5 for each). + - After sources, append “REASONING:” with the bias-analysis reasoning string. +- The backend returns only the final formatted summary string; UI renders it verbatim. + +## Notes +- No Gemini model caching (cloud API). +- Local embedding model (SentenceTransformers) is cached once and reused. +- Optional whitelist filtering toggled via USE_WHITELIST_ONLY in .env. \ No newline at end of file diff --git a/bias_analyzer.py b/bias_analyzer.py new file mode 100644 index 0000000000000000000000000000000000000000..9331630ab3807a0d190534267dc1eaa7afccbfe9 --- /dev/null +++ b/bias_analyzer.py @@ -0,0 +1,470 @@ +""" +bias_analyzer.py - Module for bias analysis, categorization, and summarization + +This module provides functions for: +1. Analyzing bias in news sources +2. Categorizing articles by bias category +3. Creating embeddings for each category +4. Summarizing information from unbiased sources +""" + +import os +import re +import json +import google.generativeai as genai +from dotenv import load_dotenv + +# Import from ranker to use the shared model cache and device detection +from ranker import DEVICE, _MODEL_CACHE + +# Load environment variables +load_dotenv() + +# Set up the Gemini API with the API key from environment variables +GEMINI_API_KEY = os.getenv('GEMINI_API_KEY') +if not GEMINI_API_KEY: + raise ValueError("GEMINI_API_KEY environment variable not set. Please add it to your .env file.") + +# Initialize the Gemini client +genai.configure(api_key=GEMINI_API_KEY) + + +def analyze_bias(query, outlet_names, model_name): + """ + Analyzes bias in news sources and categorizes them. + + Args: + query (str): The original user query + outlet_names (list): List of unique outlet names to categorize + model_name (str): The name of the Gemini model to use + + Returns: + dict: Dictionary containing bias analysis results and categorized articles + """ + # Only print status messages if debug mode is enabled + print(f"Analyzing potential bias in {len(outlet_names)} unique news outlets...") + + try: + # Define system prompt for bias analysis + bias_analysis_prompt = """ + 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. + + 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. + + For example, depending on the query and outlets, your categories might be: + - Query about climate change: "industry-funded", "environmental-activist", "unbiased" + - Query about international conflict: "pro-western", "state-controlled", "anti-western", "regional-perspective", "unbiased" + - Query about economic policy: "pro-business", "labor-oriented", "progressive", "conservative", "unbiased" + + Consider these factors: + - Historical reporting patterns and perspectives + - Ownership and financial interests + - Terminology and framing used in headlines + - Fact-based vs. opinion-heavy reporting + - Regional or national interests that may influence coverage + + CRITICAL REQUIREMENT: One category MUST be exactly named "unbiased" (not "neutral", "balanced", "centrist", or any other variation). + + Return your response in the exact JSON format shown below: + { + "categories": { + "bias category 1": [list of outlet names in this category], + "bias category 2": [list of outlet names in this category], + "bias category 3": [list of outlet names in this category], + "bias category 4": [list of outlet names in this category], + "unbiased": [list of outlet names that are generally neutral] + }, + "descriptions": { + "bias category 1": "A concise description of what this category represents", + "bias category 2": "A concise description of what this category represents", + "bias category 3": "A concise description of what this category represents", + "bias category 4": "A concise description of what this category represents", + "unbiased": "A concise description of what this category represents" + }, + "reasoning": "A brief explanation of your overall categorization approach" + } + + 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. + + Replace "bias category 1", "bias category 2", etc. with meaningful names that describe the bias types you've identified. + Be comprehensive and put every outlet in exactly one category. + IMPORTANT: You MUST name one category exactly "unbiased" (lowercase) without any variation. + """ + + # Use the original query directly + corrected_query = query + + # Prepare input for Gemini + input_text = f"Query: {corrected_query}\n\nNews Sources:\n" + "\n".join(outlet_names) + #print(input_text) + # Initialize Gemini client and generate analysis + model = genai.GenerativeModel(model_name) + + generation_config = genai.GenerationConfig( + temperature=0.1, # Lower temperature for more deterministic results + ) + + response = model.generate_content( + f"{bias_analysis_prompt}\n\n{input_text}", + generation_config=generation_config + ) + + # Parse the JSON response + response_text = response.text + json_match = re.search(r'{[\s\S]*}', response_text) + + if json_match: + parsed_response = json.loads(json_match.group(0)) + + # Handle the new JSON structure with categories and descriptions + bias_analysis = {} + category_descriptions = {} + + # Extract categories and descriptions from the new format + if 'categories' in parsed_response: + # New format + categories = parsed_response.get('categories', {}) + category_descriptions = parsed_response.get('descriptions', {}) + reasoning = parsed_response.get('reasoning', 'No reasoning provided') + + # Make sure one category is exactly named "unbiased" + # If there's a similar category (like "neutral"), rename it to "unbiased" + has_unbiased = "unbiased" in categories + if not has_unbiased: + similar_category = None + for cat in list(categories.keys()): + if cat.lower() in ["neutral", "center", "balanced", "objective", "impartial"]: + similar_category = cat + break + + # Rename the similar category to "unbiased" if found + if similar_category: + categories["unbiased"] = categories.pop(similar_category) + if similar_category in category_descriptions: + category_descriptions["unbiased"] = category_descriptions.pop(similar_category) + + # Copy categories to the top level for backward compatibility + for category, outlets in categories.items(): + bias_analysis[category] = outlets + else: + # Old format for backward compatibility + for key, value in parsed_response.items(): + if key != "reasoning": + bias_analysis[key] = value + reasoning = parsed_response.get('reasoning', 'No reasoning provided') + + # Add reasoning + bias_analysis["reasoning"] = reasoning + + # Add descriptions to the analysis result + bias_analysis["descriptions"] = category_descriptions + + # Print bias categories + print("\nBias Analysis Results:") + # Print information about each category + for key, values in bias_analysis.items(): + if key not in ["reasoning", "descriptions"]: + print(f"{key}: {len(values)} sources") + + print(f"\nReasoning: {bias_analysis.get('reasoning', 'No reasoning provided')}") + + return bias_analysis + else: + print("⚠️ Could not parse bias analysis response from Gemini.") + # Return an empty analysis with at least unbiased category if parsing fails + return { + "unbiased": [], + "reasoning": "Failed to analyze bias" + } + + except Exception as e: + print(f"⚠️ Error in bias analysis module: {e}") + # Return an empty analysis with at least unbiased category if an error occurs + return { + "unbiased": [], + "reasoning": f"Error in analysis: {str(e)}" + } + + +def categorize_and_rank_by_bias(query, normalized_articles, bias_analysis, ranker, min_threshold): + """ + Categorizes articles by bias category and creates embeddings for each category. + + Args: + query (str): The original user query + normalized_articles (list): List of normalized article dictionaries + bias_analysis (dict): The bias analysis results + ranker: The ArticleRanker instance + min_threshold (float): Minimum similarity threshold + + Returns: + dict: Dictionary containing ranked articles by category + """ + # Get number of articles per category from environment variable + top_n_per_category = int(os.getenv('TOP_N_PER_CATEGORY', 5)) + + # Extract the category names from the bias analysis + categories = [] + for key in bias_analysis.keys(): + if key not in ["reasoning", "descriptions"]: + categories.append(key) + + # Initialize dictionaries for categorized articles + categorized_articles = {} + for category in categories: + categorized_articles[category] = [] + + # Categorize articles based on their source + for article in normalized_articles: + # Extract source name (handling both object and string formats) + if isinstance(article['source'], dict): + source_name = article['source'].get('name', '') + else: + source_name = article['source'] + + # Check each category to see if this source belongs to it + for category in categories: + if source_name in bias_analysis.get(category, []): + categorized_articles[category].append(article) + break + + # Create separate embeddings for each category + category_rankings = {} + + for category, articles in categorized_articles.items(): + if not articles: + category_rankings[category] = [] + continue + + print(f"Creating embeddings for {len(articles)} articles in '{category}' category...") + + # Prepare article texts for this category - ONLY USE TITLES + category_texts = [article['title'] for article in articles] + + try: + # Create embeddings for this category + query_embedding, category_embeddings = ranker.create_embeddings(query, category_texts) + + # Calculate similarities + similarities = ranker.calculate_similarities(query_embedding, category_embeddings) + + # Get top articles for this category + top_indices = ranker.get_top_articles( + similarities, + articles, + min(top_n_per_category, len(articles)), + min_threshold + ) + + # Format results for this category + category_rankings[category] = ranker.format_results(top_indices, similarities, articles) + + # No need to print articles here since they will be printed in main.py + + except Exception as e: + print(f"⚠️ Error ranking articles for '{category}' category: {e}") + category_rankings[category] = [] + + return category_rankings + + +def generate_summary(query, normalized_articles, category_rankings, model_name): + """ + Generates a summary using articles from all categories, clearly identifying each category's sources. + + Args: + query (str): The original user query + normalized_articles (list): List of normalized article dictionaries + category_rankings (dict): The ranked articles by category + model_name (str): The name of the Gemini model to use + + Returns: + str: The generated summary + """ + # Extract the reasoning from category_rankings if available + reasoning = category_rankings.get("reasoning", "No reasoning provided") if isinstance(category_rankings, dict) else "No reasoning provided" + + # Check if we have any articles for summarization + if not category_rankings or all(not articles for category, articles in category_rankings.items() + if category not in ["descriptions", "reasoning"]): + print("No articles available for summarization.") + return "No articles available for summarization." + + # Define system prompt for summarization + summarization_prompt = """ + 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. + + The articles provided will be clearly labeled with their bias category. You will receive articles from different perspectives: + Articles from the "unbiased" category are generally considered neutral and factual + Articles from other categories may represent specific perspectives or biases + + Guidelines: + 1. Focus primarily on verifiable facts that appear across multiple sources + 2. Highlight areas of consensus across sources from different categories + 3. Note significant differences in how different categories report on the same events + 4. Maintain neutral language in your summary despite potential bias in the sources + 5. Include relevant dates, figures, and key details + 6. Prioritize information that directly answers the user's query + 7. Acknowledge different perspectives when they exist + + 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. + + Structure your response in these sections: + 1. SUMMARY A 3 to 5 sentence factual answer to the query that balances all perspectives + 2. KEY FACTS 4 to 6 numbered points with the most important verified information (use numbers only, no symbols) + 3. DIFFERENT PERSPECTIVES Brief explanation of how different sources frame the issue + 4. SOURCES BY CATEGORY + Group sources under their respective categories (UNBIASED SOURCES, CATEGORY 1 SOURCES, etc.) + Under each category heading, list UP TO 5 URLs of sources from that category + Number sources starting from 1 within EACH category (each category has its own 1 to 5 numbering) + Include only the source name, date, and URL for each source + Format: 1. source.com (date) URL https://source.com/article + + IMPORTANT + Show each category as a separate heading with the category name in ALL CAPS + List all sources from the same category together under their category heading + Each category should have its OWN numbering from 1 to 5 (do NOT number continuously across categories) + Include URLs for each source, clearly labeled + Show up to 5 sources PER CATEGORY (not 5 total) + DO NOT use any special characters or symbols such as hash (#), asterisk (*), hyphen (-), underscore (_) + + Be accurate, concise, and provide a balanced view that acknowledges different perspectives. + """ + + # We'll limit to a maximum of 30 articles total (to avoid overloading Gemini) + # but we'll make sure each category is represented + article_info = [] + article_number = 1 + max_articles_total = 30 + + # Count the number of non-empty categories + valid_categories = [cat for cat in category_rankings.keys() + if cat not in ["descriptions", "reasoning"] and category_rankings[cat]] + + # Calculate how many articles to take from each category to maintain balance + # Ensure we try to get at least 5 per category when possible + articles_per_category = min(10, max(5, max_articles_total // len(valid_categories))) if valid_categories else 0 + + # Process articles from each category + for category, articles in category_rankings.items(): + # Skip non-category keys + if category in ["descriptions", "reasoning"]: + continue + + # Skip empty categories + if not articles: + continue + + # Get limited articles for this category + top_articles = articles[:min(articles_per_category, len(articles))] + + # Add category header + article_info.append(f"\n===== ARTICLES FROM {category.upper()} CATEGORY =====\n\n") + + # Extract article information for this category + for article in top_articles: + # Find the full article data from normalized_articles + for full_article in normalized_articles: + if full_article['url'] == article['url']: + # Extract source name (handling both object and string formats) + if isinstance(full_article['source'], dict): + source_name = full_article['source'].get('name', '') + else: + source_name = full_article['source'] + + # Use the appropriate date field + published_date = full_article.get('publishedAt', full_article.get('published_at', '')) + + # Get description content, ensuring we have at least some text + description = full_article.get('description', '') + if not description and 'content' in full_article: + description = full_article['content'] + if not description: + description = "No content available. Using title only." + + article_info.append( + f"ARTICLE {article_number} ({category.upper()}):\n" + f"Title: {full_article['title']}\n" + f"Source: {source_name}\n" + f"URL: {full_article['url']}\n" + f"Date: {published_date}\n" + f"Content: {description}\n\n" + ) + article_number += 1 + break + else: + # If we didn't find the full article, use what we have from the ranked article + article_info.append( + f"ARTICLE {article_number} ({category.upper()}):\n" + f"Title: {article['title']}\n" + f"Source: {article['source']}\n" + f"URL: {article['url']}\n" + f"Date: {article['published_at']}\n" + f"Content: No detailed content available.\n\n" + ) + article_number += 1 + + # Prepare input for Gemini + input_text = f"""USER QUERY: {query} + +IMPORTANT INSTRUCTIONS: +- Group sources by their categories with clear headings (e.g., UNBIASED SOURCES, CATEGORY 1 SOURCES) +- List UP TO 5 URLs under EACH category heading +- Each category should have its OWN numbering from 1-5 (restart at 1 for each category) +- Show up to 5 sources PER CATEGORY (not 5 total) +- Format each source as: #1. source.com (date) - URL: https://source.com/article + +{''.join(article_info)}""" + + try: + # Initialize Gemini client and generate summary + model = genai.GenerativeModel(model_name) + + generation_config = genai.GenerationConfig( + temperature=0.2, # Moderate temperature for factual but natural summary + ) + + # Get additional instructions to format the output with clear source categorization + post_processing_instructions = """ +Please ensure your final output follows these formatting guidelines: +1. SUMMARY section should be at the top +2. KEY FACTS section should follow the summary +3. DIFFERENT PERSPECTIVES section should be after key facts +4. SOURCES BY CATEGORY section should: + - Group sources by their categories (e.g., UNBIASED SOURCES, CATEGORY 1 SOURCES, etc.) + - List each category as a separate heading in ALL CAPS + - Show UP TO 5 URLs clearly under each category heading + - Each category should have its OWN numbering from 1-5 (restart at 1 for each category) + - Show the MOST RELEVANT sources from each category +""" + + # Prepare the prompt with formatting instructions + prompt_with_formatting = f"{summarization_prompt}\n\n{post_processing_instructions}\n\n{input_text}" + + response = model.generate_content( + prompt_with_formatting, + generation_config=generation_config + ) + + # Format the response with consistent styling similar to the input query display in UI + summary_text = response.text + + # Create formatted summary with consistent styling + formatted_summary = f""" +MULTI-PERSPECTIVE FACTUAL SUMMARY: + +{summary_text} + +ANALYSIS REASONING: + +{reasoning} + +""" + + # Return the formatted summary + return formatted_summary + + except Exception as e: + error_msg = f"⚠️ Error generating summary: {e}" + print(error_msg) + return error_msg diff --git a/check_models.py b/check_models.py new file mode 100644 index 0000000000000000000000000000000000000000..1d77e6b555ace319ba708159b005c93624b193dc --- /dev/null +++ b/check_models.py @@ -0,0 +1,21 @@ +""" +Check available models in Google Generative AI. +""" + +import os +import google.generativeai as genai +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +# Set up the Gemini API with the API key +GEMINI_API_KEY = os.getenv('GEMINI_API_KEY') +if not GEMINI_API_KEY: + raise ValueError("GEMINI_API_KEY environment variable not set") + +genai.configure(api_key=GEMINI_API_KEY) + +# List available models +for model in genai.list_models(): + print(model.name) diff --git a/gdelt_api.py b/gdelt_api.py new file mode 100644 index 0000000000000000000000000000000000000000..0e29077b87a86250d1f7f13347b125834676ccfb --- /dev/null +++ b/gdelt_api.py @@ -0,0 +1,193 @@ +""" +gdelt_api.py - Module for interacting with the GDELT API + +This module provides functions for fetching articles from GDELT +and filtering by trusted domains. +""" + +import os +import requests +import datetime +from urllib.parse import urlparse +from dotenv import load_dotenv + +# Import the whitelist from the separate module +from whitelisted_domains import WHITELISTED_DOMAINS + + +def fetch_articles_from_gdelt(query): + """ + Fetch news articles from GDELT API. + + Args: + query (str): The news query to search for (can be structured GDELT query) + + Returns: + list: List of article dictionaries, or empty list if no results or error + """ + try: + # Construct the API URL with the query + base_url = "https://api.gdeltproject.org/api/v2/doc/doc" + + # Parse the query to separate main query from other parameters + params = { + 'format': 'json', + 'maxrecords': int(os.getenv('MAX_ARTICLES_PER_QUERY', 250)) + } + + # If the query has multiple parameters (separated by &) + if '&' in query: + parts = query.split('&') + for part in parts: + if '=' in part: + key, value = part.split('=', 1) + params[key] = value + elif part.startswith('query='): + params['query'] = part[6:] # Remove 'query=' + else: + # If it's just a query without the query= prefix + if 'query' not in params: + params['query'] = part + else: + # It's just a simple query + if query.startswith('query='): + params['query'] = query[6:] # Remove 'query=' + else: + params['query'] = query + + # Convert params to query string + query_string = "&".join([f"{k}={requests.utils.quote(str(v))}" for k, v in params.items()]) + url = f"{base_url}?{query_string}" + + print(f"DEBUG - Requesting URL: {url}") + + # Make the request + response = requests.get(url, timeout=30) + + # Check if the request was successful + if response.status_code == 200: + data = response.json() + + # GDELT API returns articles in the 'articles' field + if 'articles' in data: + return data['articles'] + + return [] + + except Exception as e: + print(f"⚠️ Error fetching articles from GDELT: {e}") + return [] + + +def is_whitelisted_domain(url): + """ + Check if the URL belongs to a whitelisted domain. + + Args: + url (str): The URL to check + + Returns: + bool: True if the domain is whitelisted, False otherwise + """ + try: + domain = urlparse(url).netloc + + # Handle subdomains by checking if any whitelisted domain is a suffix + return any(domain.endswith(trusted_domain) for trusted_domain in WHITELISTED_DOMAINS) + except: + return False + + +def format_timestamp(timestamp_str): + """ + Format a GDELT timestamp string to a more user-friendly format. + + Args: + timestamp_str (str): Timestamp string in GDELT format (e.g., "20250829T173000Z") + + Returns: + str: Formatted timestamp (e.g., "Aug 29, 2025 17:30") + """ + if not timestamp_str: + return "" + + try: + # Handle common GDELT timestamp format + if "T" in timestamp_str and len(timestamp_str) >= 15: + # Parse YYYYMMDDTHHMMSSZ format + year = timestamp_str[0:4] + month = timestamp_str[4:6] + day = timestamp_str[6:8] + hour = timestamp_str[9:11] + minute = timestamp_str[11:13] + + # Convert month number to month name + month_name = datetime.datetime.strptime(month, "%m").strftime("%b") + + return f"{month_name} {int(day)}, {year} {hour}:{minute}" + else: + # Return original if not in expected format + return timestamp_str + except Exception as e: + print(f"Error formatting timestamp {timestamp_str}: {e}") + return timestamp_str + + +def filter_by_whitelisted_domains(articles): + """ + Filter articles to only include those from trusted domains. + + Args: + articles (list): List of article dictionaries + + Returns: + list: Filtered list of article dictionaries + """ + if not articles: + return [] + + trusted_articles = [] + total_articles = len(articles) + + for article in articles: + if 'url' in article and is_whitelisted_domain(article['url']): + trusted_articles.append(article) + + filtered_count = total_articles - len(trusted_articles) + print(f"Domain filtering: {filtered_count} non-whitelisted articles removed, {len(trusted_articles)} whitelisted articles kept") + + return trusted_articles + + +def normalize_gdelt_articles(articles): + """ + Normalize GDELT article format to match the expected format in the rest of the application. + + Args: + articles (list): List of GDELT article dictionaries + + Returns: + list: List of normalized article dictionaries + """ + normalized_articles = [] + + for article in articles: + # Extract domain from URL for source name if not available + source_name = article.get('sourcename', '') + if not source_name and 'url' in article: + domain = urlparse(article['url']).netloc + source_name = domain.replace('www.', '') + + # Format the timestamp for better readability + raw_timestamp = article.get('seendate', '') + formatted_timestamp = format_timestamp(raw_timestamp) + + normalized_articles.append({ + 'title': article.get('title', ''), + 'description': article.get('seentext', ''), + 'url': article.get('url', ''), + 'publishedAt': formatted_timestamp, + 'source': {'name': source_name} + }) + + return normalized_articles diff --git a/gdelt_query_builder.py b/gdelt_query_builder.py new file mode 100644 index 0000000000000000000000000000000000000000..2e73f970dc9040a2c54793871e762ec56476cfff --- /dev/null +++ b/gdelt_query_builder.py @@ -0,0 +1,202 @@ +""" +gdelt_query_builder.py - Module for building GDELT queries using Gemini API + +This module uses Google's Gemini API to transform natural language queries +into structured GDELT query format. +""" + +import os +import google.generativeai as genai +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +# Set up the Gemini API with the API key from environment variables +GEMINI_API_KEY = os.getenv('GEMINI_API_KEY') +if not GEMINI_API_KEY: + raise ValueError("GEMINI_API_KEY environment variable not set. Please add it to your .env file.") + +# Initialize the Gemini client +genai.configure(api_key=GEMINI_API_KEY) + +# Get the model name from environment variables or use default +GEMINI_MODEL = os.getenv('GEMINI_MODEL', 'gemini-2.5-flash') + +# Define the system prompt for Gemini +SYSTEM_PROMPT = """ +You are a query builder for the GDELT 2.0 DOC API. +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. + +IMPORTANT: First, check if the query contains inappropriate content such as: +- Pornography or sexually explicit content + + +If the query contains ANY of the above, respond EXACTLY with the string: +"INAPPROPRIATE_QUERY_DETECTED" + +Otherwise, proceed with the following rules: + +Rules: +1. IMPORTANT: Always keep the query variations in the SAME LANGUAGE as the user's original query. +2. Correct spelling mistakes in the user input before processing. +3. Remove all words with length less than or equal to 2 characters ONLY if they don't affect meaning. +4. ONLY use AND operators between terms. DO NOT use OR or NOT operators. +5. Create TWO TYPES of variations: + a. For the first 5 variations: Create journalistic style queries with verbs and complete phrases (e.g., "announced sanctions", "threatens military action") + 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") +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. + - Replace short terms (< 5 chars) with equivalent longer terms (e.g., "UK" → "United Kingdom", "US" → "United States", "EU" → "Europe") + - For common short words with no longer alternative, add context words to make them more specific +7. Contextual understanding: + - ALWAYS analyze the query for implied countries, regions, people, or events that suggest a location + - For people (e.g., "Biden", "Putin", "Modi"), infer their associated country + - For events (e.g., "Olympics in Paris", "Earthquake in Japan"), extract the location + - For organizations (e.g., "EU Parliament", "Kremlin"), map to their appropriate region +7. Country and region parameters: + - Always include sourcecountry and/or sourceregion when you can infer them from context + - If user mentions a country, include it as: sourcecountry= + - If user mentions a region, include it as: sourceregion= + - For Europe use: sourceregion=EU (not a country code) + - For implicit locations (e.g., "Eiffel Tower" → France), add the appropriate country code +8. Time range detection: + - Analyze for any time-related terms ("yesterday", "last week", "June 2025") + - Include startdatetime and enddatetime for any time reference (format YYYYMMDDHHMMSS) + - For relative times like "last week" or "recent", calculate actual date ranges +9. Main query construction: + - Always format as: query= + - For exact phrases use double quotes: query="climate change" + - Connect concepts with AND ONLY: query="climate change" AND "global warming" + - Each query should be well-formed with proper placement of operators and quotes +10. Language preservation: + - If the user's query is in English, all variations must be in English + - If the user's query is in Spanish, all variations must be in Spanish + - If the user's query is in French, all variations must be in French + - Always maintain the original language of the user's query in all variations +11. Output format: + - Return EXACTLY ten query variations, separated by ||| (three pipe symbols) + - For the first 5 variations, focus on journalistic style with complete phrases + - For the last 5 variations, focus ONLY on organizations, entities, locations, and relevant nouns WITHOUT any verbs or phrases of speech + - Example for entity-only variations: query="United Nations" AND "climate change" AND "Paris" + - Each query should be a complete, valid GDELT query string + - Format each query correctly with query= at the beginning and & between parameters + - Do not explain your choices, just return the ten queries + +Examples: +- Input: "Did a tsunami really happen in Japan yesterday?" + 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 + +- Input: "Is it true that there was an explosion near the Eiffel Tower?" + 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 + +- Input: "Did Biden announce new sanctions against Russia last week?" + 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 + +- Input: "¿Hubo una manifestación en Madrid ayer?" + 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 + +- Input: "Was a new law on AI passed in UK?" + 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 +""" + + +def generate_query(user_input: str) -> list: + """ + Generate ten structured GDELT queries from natural language input using Gemini API. + + Args: + user_input (str): The user's natural language query + + Returns: + list: List of ten structured GDELT query variations or a list with a single + inappropriate content message if the query contains sensitive topics + """ + try: + # Create the chat with system prompt and user input + combined_prompt = f"{SYSTEM_PROMPT}\n\nUser request: {user_input}" + + # Generate content with the specified model + model = genai.GenerativeModel(GEMINI_MODEL) + + # Set generation config to disable thinking + generation_config = genai.GenerationConfig( + temperature=0.3, # Slightly higher temperature for more variation + ) + + response = model.generate_content( + combined_prompt, + generation_config=generation_config + ) + + # Extract the response text + response_text = response.text.strip() + + # Check if the model detected inappropriate content + if response_text == "INAPPROPRIATE_QUERY_DETECTED": + print(f"⚠️ Inappropriate query detected: '{user_input}'") + # Return a special marker that will be detected in main.py + return ["INAPPROPRIATE_QUERY"] + + # Split the response by the separator + query_variations = response_text.split('|||') + + # Clean and format each query variation + formatted_queries = [] + for i, query in enumerate(query_variations): + query = query.strip() + + # Ensure query format is correct - add "query=" if needed + if not (query.startswith('query=') or + 'sourcecountry=' in query or + 'sourceregion=' in query or + 'startdatetime=' in query): + # Add query= prefix if not dealing with just filter parameters + query = f'query={query}' + + formatted_queries.append(query) + + # If we don't have exactly 10 queries, duplicate or trim as needed + while len(formatted_queries) < 10: + formatted_queries.append(formatted_queries[0]) + + if len(formatted_queries) > 10: + formatted_queries = formatted_queries[:10] + + # Log the transformation only once + print(f"Original input: '{user_input}'") + for i, query in enumerate(formatted_queries): + print(f"Query variation {i+1}: '{query}'") + + return formatted_queries + + except Exception as e: + if "429" in str(e): + print(f"⚠️ Rate limit exceeded for Gemini API. Using original query. Details: {e}") + elif "404" in str(e) and "models" in str(e): + print(f"⚠️ Model not found. Please check available models. Details: {e}") + elif "400" in str(e) and "API key" in str(e): + print(f"⚠️ Invalid API key. Please check your GEMINI_API_KEY in .env file. Details: {e}") + else: + print(f"⚠️ Error generating structured query: {e}") + + # Format the original query with quotes for GDELT for all ten variations + fallback_query = f'query="{user_input}"' + return [fallback_query] * 10 + + +if __name__ == "__main__": + # Example usage + test_queries = [ + "Latest news about climate change in Europe", + "Political developments in Ukraine last week", + "Economic impact of recent floods in Asia", + "Noticias sobre cambio climático en España" # Spanish query + ] + + print("\nGDELT Query Builder - Example Usage\n") + for query in test_queries: + print(f"\nTesting query: {query}") + query_variations = generate_query(query) + for i, variation in enumerate(query_variations): + print(f"Variation {i+1}: {variation}") + print("-" * 80) diff --git a/google_search.py b/google_search.py new file mode 100644 index 0000000000000000000000000000000000000000..987a2b9210daf0aa1e0dab2e4d602128f7ccdab8 --- /dev/null +++ b/google_search.py @@ -0,0 +1,64 @@ +import requests +import os +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +# Get SerpAPI key from environment variables +API_KEY = os.getenv('SERPAPI_KEY') +if not API_KEY: + raise ValueError("SERPAPI_KEY environment variable not set. Please add it to your .env file.") + +def google_search(query, num_results=25): + """ + Search Google for articles related to the query using SerpAPI. + + Args: + query (str): The search query + num_results (int): Number of results to return + + Returns: + list: List of article dictionaries with title, snippet, and URL + """ + url = "https://serpapi.com/search" + params = { + "engine": "google", # search engine + "q": query, # search query + "api_key": API_KEY, # your key + "num": num_results # number of results + } + + try: + response = requests.get(url, params=params) + response.raise_for_status() # Raise exception for HTTP errors + results = response.json() + + output = [] + for item in results.get("organic_results", []): + # Format the results to match GDELT article format for consistency + article = { + "title": item.get("title", "No title"), + "snippet": item.get("snippet", "No description available"), + "url": item.get("link", ""), + "source": { + "name": item.get("source", "Google Search") + }, + "publishedAt": "", # No date information in the API response + "origin": "google_search" # Add origin to track source + } + output.append(article) + + print(f"Found {len(output)} results from Google Search") + return output + + except Exception as e: + print(f"Error with Google Search API: {e}") + return [] + +# Example usage +if __name__ == "__main__": + query = "GM stopped operations in India" + search_results = google_search(query) + for r in search_results: + print(f"{r['title']}\n{r['snippet']}\n{r['link']}\n") diff --git a/main.py b/main.py new file mode 100755 index 0000000000000000000000000000000000000000..f853ece92619afaaf8e1bb539ec7a43ed739dd82 --- /dev/null +++ b/main.py @@ -0,0 +1,345 @@ +#!/home/tom/miniconda3/envs/fake_news_detection/bin/python +""" +main.py - Server for the Fake News Detection system + +This script creates a Flask server that exposes API endpoints to: +1. Take user input (news query) from the UI +2. Process the request through the fake news detection pipeline +3. Return the results to the UI for display +""" + +import os +import json +import time +from dotenv import load_dotenv +from flask import Flask, request, jsonify +from flask_cors import CORS + +# Import required functions from modules +from gdelt_api import ( + fetch_articles_from_gdelt, + filter_by_whitelisted_domains, + normalize_gdelt_articles +) +from ranker import ArticleRanker +from gdelt_query_builder import generate_query, GEMINI_MODEL +import bias_analyzer +from google_search import google_search + +# Global variable for embedding model caching across requests +print("Preloading embedding model for faster request processing...") +# Preload the embedding model at server startup +global_ranker = ArticleRanker() + + +# The function has been removed since bias category descriptions are provided directly by the Gemini model +# and stored in the bias_analysis["descriptions"] dictionary + + +def format_results(query, ranked_articles): + """ + Format the ranked results in a structured way for the UI. + + Args: + query (str): The original query + ranked_articles (list): List of ranked article dictionaries + + Returns: + dict: Dictionary with formatted results + """ + result = {} + + if not ranked_articles: + result = { + "status": "no_results", + "message": "⚠️ No news found. Possibly Fake.", + "details": "No reliable sources could verify this information.", + "articles": [] + } + else: + # Get display configuration from environment variables + show_scores = os.getenv('SHOW_SIMILARITY_SCORES', 'true').lower() == 'true' + show_date = os.getenv('SHOW_PUBLISH_DATE', 'true').lower() == 'true' + show_url = os.getenv('SHOW_URL', 'true').lower() == 'true' + + formatted_articles = [] + for article in ranked_articles: + formatted_article = { + "rank": article['rank'], + "title": article['title'], + "source": article['source'] + } + + if show_scores: + formatted_article["similarity_score"] = round(article['similarity_score'], 4) + + if show_url: + formatted_article["url"] = article['url'] + + if show_date: + formatted_article["published_at"] = article['published_at'] + + formatted_articles.append(formatted_article) + + result = { + "status": "success", + "message": f"✅ Found {len(ranked_articles)} relevant articles for: '{query}'", + "articles": formatted_articles, + "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." + } + + return result + + +def remove_duplicates(articles): + """ + Remove duplicate articles based on URL. + + Args: + articles (list): List of article dictionaries + + Returns: + list: List with duplicate articles removed + """ + unique_urls = set() + unique_articles = [] + + for article in articles: + if article['url'] not in unique_urls: + unique_urls.add(article['url']) + unique_articles.append(article) + + return unique_articles + + +# This function has been removed since Gemini is a cloud API service +# that does not require local caching - models are instantiated as needed + + +def main(): + """Main function to run the fake news detection pipeline as a server.""" + # Load environment variables + load_dotenv() + + # Create Flask app + app = Flask(__name__, static_folder='static') + CORS(app) # Enable CORS for all routes + + @app.route('/static/') + def index(): + """Serve the main page.""" + return app.send_static_file('front.html') + + + @app.route('/api/detect', methods=['POST']) + def detect_fake_news(): + """API endpoint to check if news is potentially fake.""" + # Start timing the request processing + start_time = time.time() + + data = request.json + query = data.get('query', '') + + if not query: + return jsonify({ + "status": "error", + "message": "Please provide a news statement to verify." + }) + + # ===================================================== + # 1. Input Handling + # ===================================================== + # Generate three variations of the query using Gemini + query_variations = generate_query(query) + + # Check if the query was flagged as inappropriate + if query_variations == ["INAPPROPRIATE_QUERY"]: + return jsonify({ + "status": "error", + "message": "I cannot provide information on this topic as it appears to contain sensitive or inappropriate content." + }) + + # ===================================================== + # 2. Data Fetching + # ===================================================== + # Fetch articles from GDELT API for each query variation + all_articles = [] + + # First, fetch Google search results using the original query + # print(f"Fetching Google search results for: {query}") + # google_results = google_search(query, num_results=25) + # if google_results: + # all_articles.extend(google_results) + # print(f"Added {len(google_results)} Google search results to articles") + + # Then fetch GDELT results for each query variation + for query_var in query_variations: + articles = fetch_articles_from_gdelt(query_var) + if articles: + all_articles.extend(articles) + + # After the loop, check if any articles were found + if not all_articles: + return jsonify({ + "status": "no_results", + "message": "No articles found on this topic.", + "details": "No reliable sources could be found covering this information.", + "articles": [] + }) + + # Store unique articles in a set to ensure uniqueness + unique_articles = remove_duplicates(all_articles) + + # Apply domain whitelist filtering if enabled in .env + use_whitelist_only = os.getenv('USE_WHITELIST_ONLY', 'false').lower() == 'true' + if use_whitelist_only: + print(f"Filtering articles to only include whitelisted domains...") + unique_articles = filter_by_whitelisted_domains(unique_articles) + print(f"After whitelist filtering: {len(unique_articles)} articles remain") + + # Normalize the articles to a standard format + normalized_articles = normalize_gdelt_articles(unique_articles) + + if not normalized_articles: + return jsonify(format_results(query, [])) + + # ===================================================== + # 3. Embedding & Ranking + # ===================================================== + # Initialize the ranker with model from environment variable + model_name = os.getenv('SIMILARITY_MODEL', 'intfloat/multilingual-e5-base') + + # Use global ranker if it matches the requested model, otherwise create a new instance + if global_ranker.model_name == model_name: + ranker = global_ranker + else: + ranker = ArticleRanker(model_name) + + # Get TOP_K_ARTICLES from .env file + TOP_K_ARTICLES = int(os.getenv('TOP_K_ARTICLES', 250)) + min_threshold = float(os.getenv('MIN_SIMILARITY_THRESHOLD', 0.1)) + + # Prepare article texts for embedding + article_texts = [f"{article['title']} {article['description'] or ''}" for article in normalized_articles] + + # Create embeddings and calculate similarities + query_embedding, article_embeddings = ranker.create_embeddings(query, article_texts) + similarities = ranker.calculate_similarities(query_embedding, article_embeddings) + + # Get top articles based on similarity + top_indices = ranker.get_top_articles(similarities, normalized_articles, TOP_K_ARTICLES, min_threshold) + top_articles = ranker.format_results(top_indices, similarities, normalized_articles) + + # ===================================================== + # 4. Bias Categorization + # ===================================================== + # Extract outlet names from the TOP_K_ARTICLES + # In top_articles, the source is already extracted as a string + outlet_names = [article['source'] for article in top_articles] + unique_outlets = list(set(outlet_names)) + print(f"Analyzing {len(unique_outlets)} unique news outlets for bias...") + + # Analyze bias using Gemini - send just the outlet names, not the whole articles + bias_analysis = bias_analyzer.analyze_bias(query, unique_outlets, GEMINI_MODEL) + + # ===================================================== + # 5. Category Embeddings + # ===================================================== + print("\n" + "=" * 80) + print("EMBEDDING VECTORS BY BIAS CATEGORY") + print("=" * 80) + + # Create embedding vectors for each bias category + # 1. Group articles based on their outlet's bias category + # 2. Create an embedding vector for each category using ONLY article titles + # 3. Rank articles within each category by similarity to query + category_rankings = bias_analyzer.categorize_and_rank_by_bias( + query, normalized_articles, bias_analysis, ranker, min_threshold + ) + + # ===================================================== + # 6. Top N Selection per Category + # ===================================================== + # Get TOP_N_PER_CATEGORY from .env file (default: 5) + TOP_N_PER_CATEGORY = int(os.getenv('TOP_N_PER_CATEGORY', 5)) + + # Get total counts of articles per category before filtering + category_article_counts = { + category: len(articles) + for category, articles in category_rankings.items() + if category not in ["descriptions", "reasoning"] + } + + # For each bias category, select the top N articles + # These are the most relevant articles within each bias perspective + filtered_category_rankings = {} + for category, articles in category_rankings.items(): + # Skip non-category keys like "descriptions" or "reasoning" + if category in ["descriptions", "reasoning"]: + continue + + filtered_category_rankings[category] = articles[:TOP_N_PER_CATEGORY] + + # Only print if there are articles in this category + if len(filtered_category_rankings[category]) > 0: + print(f"\n===== Top {len(filtered_category_rankings[category])} articles from {category} category =====") + + # Print detailed information about each selected article + for i, article in enumerate(filtered_category_rankings[category], 1): + print(f"Article #{i}:") + print(f" Title: {article['title']}") + print(f" Source: {article['source']}") + print(f" Similarity Score: {article['similarity_score']:.4f}") + print(f" Rank: {article['rank']}") + print(f" URL: {article['url']}") + print(f" Published: {article['published_at']}") + print("-" * 50) + + # ===================================================== + # 7. Summarization + # ===================================================== + # Generate summary from articles in all categories + print("\nGenerating factual summary using top articles from all categories...") + + # Pass the original bias_analysis to include the reasoning in the summary + # We need to add the reasoning to filtered_category_rankings since that's what gets passed to generate_summary + filtered_category_rankings["reasoning"] = bias_analysis.get("reasoning", "No reasoning provided") + + # Call the bias_analyzer's generate_summary function with articles from all categories + summary = bias_analyzer.generate_summary( + query, + normalized_articles, + filtered_category_rankings, + GEMINI_MODEL + ) + + # Print the summary to terminal (already includes its own formatting) + print(summary) + + # Prepare response with ONLY the combined summary (reasoning already appended at end) + # Removed separate 'reasoning' key to avoid it showing at the top in the UI + result = { + "summary": summary + } + + return jsonify(result) + + @app.route('/api/health', methods=['GET']) + def health_check(): + """API endpoint to check if the server is running.""" + return jsonify({ + "status": "ok", + "message": "Fake News Detection API is running" + }) + + # Get port from environment variable or use default 5000 + port = int(os.getenv('PORT', 5000)) + debug = os.getenv('DEBUG', 'false').lower() == 'true' + + print(f"Starting Fake News Detection API server on port {port}...") + # Start the Flask server + app.run(host='0.0.0.0', port=port, debug=debug) + + +if __name__ == "__main__": + main() diff --git a/misinformationui/.gitignore b/misinformationui/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e3dafe198db61afed867f1308b8dcc609b7863aa --- /dev/null +++ b/misinformationui/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/misinformationui/.metadata b/misinformationui/.metadata new file mode 100644 index 0000000000000000000000000000000000000000..fdb4416b840505adc42edfd61ac4b8d686506bf5 --- /dev/null +++ b/misinformationui/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "fcf2c11572af6f390246c056bc905eca609533a0" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: android + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: ios + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: linux + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: macos + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: web + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: windows + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/misinformationui/README.md b/misinformationui/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3677f75cf478bfe544011b77a16d9b79176ada75 --- /dev/null +++ b/misinformationui/README.md @@ -0,0 +1,16 @@ +# misinformationui + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/misinformationui/analysis_options.yaml b/misinformationui/analysis_options.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d4e0f0c8bff70b511095207bf2c5fc9d696075f0 --- /dev/null +++ b/misinformationui/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/misinformationui/android/.gitignore b/misinformationui/android/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c9082585a535b3dee4052706248ac3782322b50a --- /dev/null +++ b/misinformationui/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/misinformationui/android/app/build.gradle.kts b/misinformationui/android/app/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..ee48efb9404ffa57c7645905c181a4fb7ebda97f --- /dev/null +++ b/misinformationui/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.misinformationui" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.misinformationui" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/misinformationui/android/app/src/debug/AndroidManifest.xml b/misinformationui/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..8ffe024642253939383a59fd6357dda4b34cb53b --- /dev/null +++ b/misinformationui/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/misinformationui/android/app/src/main/AndroidManifest.xml b/misinformationui/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..a40e943137662fafd9584f0d48c1456ac48c601e --- /dev/null +++ b/misinformationui/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/misinformationui/android/app/src/main/kotlin/com/example/misinformationui/MainActivity.kt b/misinformationui/android/app/src/main/kotlin/com/example/misinformationui/MainActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..d9c1a2e39efe6cb508ff831108f258a3a77e3939 --- /dev/null +++ b/misinformationui/android/app/src/main/kotlin/com/example/misinformationui/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.misinformationui + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/misinformationui/android/app/src/main/res/drawable-v21/launch_background.xml b/misinformationui/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..1cb7aa2f6e8c2438e50149b6ec567b515c243c88 --- /dev/null +++ b/misinformationui/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/misinformationui/android/app/src/main/res/drawable/launch_background.xml b/misinformationui/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..84037589b06bb17b24ba7acffee949159cd99a16 --- /dev/null +++ b/misinformationui/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/misinformationui/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/misinformationui/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..1be8cfe844b15e05c9de3599e39fc8ece41ecb5d --- /dev/null +++ b/misinformationui/android/app/src/main/res/mipmap-hdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a7c8f0d703e3682108f9662f813302236240d3f8f638bb391e32bfb96055fef +size 544 diff --git a/misinformationui/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/misinformationui/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..87515b2d645d212f2e45a76da6f549df4ed15f3c --- /dev/null +++ b/misinformationui/android/app/src/main/res/mipmap-mdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7c0c0189145e4e32a401c61c9bdc615754b0264e7afae24e834bb81049eaf81 +size 442 diff --git a/misinformationui/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/misinformationui/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8dab3b8d08329295ce8af5207edc3e3763974909 --- /dev/null +++ b/misinformationui/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e14aa40904929bf313fded22cf7e7ffcbf1d1aac4263b5ef1be8bfce650397aa +size 721 diff --git a/misinformationui/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/misinformationui/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..7172d5a93bb1db937dda60ff09b182d0ca16657c --- /dev/null +++ b/misinformationui/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d470bf22d5c17d84edc5f82516d1ba8a1c09559cd761cefb792f86d9f52b540 +size 1031 diff --git a/misinformationui/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/misinformationui/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4183fcce1c2d121a39442793d55184f26e14e50d --- /dev/null +++ b/misinformationui/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c34e1f298d0c9ea3455d46db6b7759c8211a49e9ec6e44b635fc5c87dfb4180 +size 1443 diff --git a/misinformationui/android/app/src/main/res/values-night/styles.xml b/misinformationui/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000000000000000000000000000000000000..360a1605cd6d544ad23ddd7515862ff2c9df6ab8 --- /dev/null +++ b/misinformationui/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/misinformationui/android/app/src/main/res/values/styles.xml b/misinformationui/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000000000000000000000000000000000..5fac679616bbc0255412f37eea3eae0a3716e7d7 --- /dev/null +++ b/misinformationui/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/misinformationui/android/app/src/profile/AndroidManifest.xml b/misinformationui/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..8ffe024642253939383a59fd6357dda4b34cb53b --- /dev/null +++ b/misinformationui/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/misinformationui/android/build.gradle.kts b/misinformationui/android/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..2f2f3f00fc713f34787b14b52d7b61279d600ddb --- /dev/null +++ b/misinformationui/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/misinformationui/android/gradle.properties b/misinformationui/android/gradle.properties new file mode 100644 index 0000000000000000000000000000000000000000..b7cda7b11fc443c494ed8ec8acd9338ee50890e7 --- /dev/null +++ b/misinformationui/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/misinformationui/android/gradle/wrapper/gradle-wrapper.properties b/misinformationui/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..dcc7e10d21f015a67e0523ffdc0db25d7d7b9c68 --- /dev/null +++ b/misinformationui/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/misinformationui/android/settings.gradle.kts b/misinformationui/android/settings.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..8ddb35debd6fb6302cf631648c3321d976edc832 --- /dev/null +++ b/misinformationui/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/misinformationui/ios/.gitignore b/misinformationui/ios/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ad322bc09d2bbd018667440c592d2a42d1ef6805 --- /dev/null +++ b/misinformationui/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/misinformationui/ios/Flutter/AppFrameworkInfo.plist b/misinformationui/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000000000000000000000000000000000000..e041d38231c74428821249d99849ce156a83354d --- /dev/null +++ b/misinformationui/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/misinformationui/ios/Flutter/Debug.xcconfig b/misinformationui/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..0b2d479c08504549ca6e74f0743bd584f068f9bd --- /dev/null +++ b/misinformationui/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/misinformationui/ios/Flutter/Release.xcconfig b/misinformationui/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..0b2d479c08504549ca6e74f0743bd584f068f9bd --- /dev/null +++ b/misinformationui/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/misinformationui/ios/Runner.xcodeproj/project.pbxproj b/misinformationui/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000000000000000000000000000000000000..d14cbf09c759c3eb502970a9f10eede20c117e35 --- /dev/null +++ b/misinformationui/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/misinformationui/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/misinformationui/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000000000000000000000000000000000..c4b79bd8c0e5b685bc62bc82f93335b2a95fd7fd --- /dev/null +++ b/misinformationui/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/misinformationui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/misinformationui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000000000000000000000000000000..fc6bf8074854f8fd9fea8d9cc80d5ca1b8d932db --- /dev/null +++ b/misinformationui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/misinformationui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/misinformationui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000000000000000000000000000000000..af0309c4dc26b0880121296bc3fd0bd14aad8420 --- /dev/null +++ b/misinformationui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/misinformationui/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/misinformationui/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..bbabc4e461f2641f43460c76ea459eb7be2f7231 --- /dev/null +++ b/misinformationui/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misinformationui/ios/Runner.xcworkspace/contents.xcworkspacedata b/misinformationui/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000000000000000000000000000000000..59c6d39465edc08bd1e919a6248c68b1b72cfd33 --- /dev/null +++ b/misinformationui/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/misinformationui/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/misinformationui/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000000000000000000000000000000..fc6bf8074854f8fd9fea8d9cc80d5ca1b8d932db --- /dev/null +++ b/misinformationui/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/misinformationui/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/misinformationui/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000000000000000000000000000000000..af0309c4dc26b0880121296bc3fd0bd14aad8420 --- /dev/null +++ b/misinformationui/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/misinformationui/ios/Runner/AppDelegate.swift b/misinformationui/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000000000000000000000000000000000000..8be1cecd13f83473b3393ca1b5998985fe108123 --- /dev/null +++ b/misinformationui/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..1950fd80edd4b0b01afb3901a2f4787251280206 --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..705cc8e346060dac80e8daa0469b5ffb04cc8254 --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7770183009e914112de7d8ef1d235a6a30c5834424858e0d2f8253f6b8d31926 +size 10932 diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0b367a64d6f96b83f504154449e26b0b3fc5f5 --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cab10a0d391ec5bc09ef50ce49e8ad401cee7ef03707ec0923a222c5c2b3d212 +size 295 diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..41715252809bcee40a5bb485e65fd2ed3dd556be --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9ad02cf6576a04d1b6806ac02a2431481b448dd0c2e505ce25842d1f7c4730b +size 406 diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..7b5cad00277eb491eb9705cc08697eaa6314c57a --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6e6d3b215ae744a9c391f4c4d44157eff5e739d6ad6c39f9bfa5df66dddd267 +size 450 diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..183a019dd87690af8e24171a1ccb36651ac21478 --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5dee24dc104ac76dc162e42ae0beb163d426bf365562ee28ba7b3ad368559a60 +size 282 diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1b3ddd451446c736c8d9e42e6b3ca1d3ff356476 --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9b21eb6f4271385655a8771f76e29eef8c1107d7879cbcfc567e6619d1f716a +size 462 diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..3b95b770a3c8221af54b31db4ba50211d038f8ef --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e677d701ffe4af7bc2935098d6b3984cc9ab7ace573e6900955a5535b12410cf +size 704 diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..41715252809bcee40a5bb485e65fd2ed3dd556be --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9ad02cf6576a04d1b6806ac02a2431481b448dd0c2e505ce25842d1f7c4730b +size 406 diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ad2b0e0fb28b8a34ab9e63d722a64f0836f85205 --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c61c42fc7b657d9cf314d32a4ec458f0647c3aaf360be1b9377857266ec2499 +size 586 diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..00ad426994dfdfe804fb6849ff0ae854c631327f --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19be171481dc71a0b2803ebcd01dd8b0c5fd5778dee34c0a3cabc948c225f24e +size 862 diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..00ad426994dfdfe804fb6849ff0ae854c631327f --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19be171481dc71a0b2803ebcd01dd8b0c5fd5778dee34c0a3cabc948c225f24e +size 862 diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..58d87f997e8ffba04cd47e7220a20e97d1e1c430 --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4209a49e44a92ec40a327d3455eb1b1c153ee83d75de1c2be0a12ab18b2ff9de +size 1674 diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..48ecfaeffd303bd4a36c9ed52b8aa55bd7677572 --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:836c918cb613249eba0483a6b02fa3df3c1c0a89a315ee4d3b88509b83c7ab73 +size 762 diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..40ce59afa8ddb3bfcbb5373bdc7437af6713ccc7 --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41c7d42f6e61f8fe7f30b1ffa2256aecbc9682be06d18c4a3062043e1a2e547c +size 1226 diff --git a/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3f6dfcc3eb15a282630e31b02cfef45b9e2fd69d --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d7e5bdf01b93802bc973345b3a78c038907147625035952a08a115a563b7f81 +size 1418 diff --git a/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..d08a4de320fe20e3d3b11b519313713413c7e21e --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..6ef7d9c4bb261db4e1185835c6d9716015ea61c0 --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93ae7d494fad0fb30cbf3ae746a39c4bc7a0f8bbf87fbb587a3f3c01f3c5ce20 +size 68 diff --git a/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ef7d9c4bb261db4e1185835c6d9716015ea61c0 --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93ae7d494fad0fb30cbf3ae746a39c4bc7a0f8bbf87fbb587a3f3c01f3c5ce20 +size 68 diff --git a/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ef7d9c4bb261db4e1185835c6d9716015ea61c0 --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93ae7d494fad0fb30cbf3ae746a39c4bc7a0f8bbf87fbb587a3f3c01f3c5ce20 +size 68 diff --git a/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000000000000000000000000000000000000..65a94b5db54ec02257c0fd4e74ebea1a7a34e881 --- /dev/null +++ b/misinformationui/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/misinformationui/ios/Runner/Base.lproj/LaunchScreen.storyboard b/misinformationui/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000000000000000000000000000000000..497371ea23a88889b582a79836d65568268f5fd3 --- /dev/null +++ b/misinformationui/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misinformationui/ios/Runner/Base.lproj/Main.storyboard b/misinformationui/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000000000000000000000000000000000000..bbb83caaec5481ea63968cdfdf6e574aa9584a1b --- /dev/null +++ b/misinformationui/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misinformationui/ios/Runner/Info.plist b/misinformationui/ios/Runner/Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..a87a6961e5247ff60f1e3446cab934592c4953ae --- /dev/null +++ b/misinformationui/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Misinformationui + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + misinformationui + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/misinformationui/ios/Runner/Runner-Bridging-Header.h b/misinformationui/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000000000000000000000000000000000000..fae207f9e2b9302728c2199314243bda2088bb8b --- /dev/null +++ b/misinformationui/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/misinformationui/ios/RunnerTests/RunnerTests.swift b/misinformationui/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..4d206ded9e1cc1a76c409afcfc90f8de4e7c99ec --- /dev/null +++ b/misinformationui/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/misinformationui/linux/.gitignore b/misinformationui/linux/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c7ea17fcf58850d5159ef3bff70925c988731c0c --- /dev/null +++ b/misinformationui/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/misinformationui/linux/CMakeLists.txt b/misinformationui/linux/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..92a231d994b98339aad39fce70aff7645997373e --- /dev/null +++ b/misinformationui/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "misinformationui") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.misinformationui") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/misinformationui/linux/flutter/CMakeLists.txt b/misinformationui/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..27860e801899e7d2a1772919c22bc2875ba541a6 --- /dev/null +++ b/misinformationui/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/misinformationui/linux/flutter/generated_plugin_registrant.cc b/misinformationui/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000000000000000000000000000000000000..f6f23bfe970ffe22ab2e64b10b6ae24575915cda --- /dev/null +++ b/misinformationui/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/misinformationui/linux/flutter/generated_plugin_registrant.h b/misinformationui/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000000000000000000000000000000000000..e0f0a47bc08f30b550b47b01de4c9206b6824dd9 --- /dev/null +++ b/misinformationui/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/misinformationui/linux/flutter/generated_plugins.cmake b/misinformationui/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000000000000000000000000000000000000..f16b4c34213acd9dbc719b4548786853e6e9503b --- /dev/null +++ b/misinformationui/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/misinformationui/linux/runner/CMakeLists.txt b/misinformationui/linux/runner/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..7ed6f3e4f57a42aca8b68af9f7ea6290780d78f3 --- /dev/null +++ b/misinformationui/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/misinformationui/linux/runner/main.cc b/misinformationui/linux/runner/main.cc new file mode 100644 index 0000000000000000000000000000000000000000..4340ffc17f21cfe31bd06431fd1129291874b2c0 --- /dev/null +++ b/misinformationui/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/misinformationui/linux/runner/my_application.cc b/misinformationui/linux/runner/my_application.cc new file mode 100644 index 0000000000000000000000000000000000000000..f8ae9e7fa25e1e370e6fba046ff4d48890a11231 --- /dev/null +++ b/misinformationui/linux/runner/my_application.cc @@ -0,0 +1,130 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "misinformationui"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "misinformationui"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/misinformationui/linux/runner/my_application.h b/misinformationui/linux/runner/my_application.h new file mode 100644 index 0000000000000000000000000000000000000000..8f20fb55aa001d950c19161e9774b0d9ae940825 --- /dev/null +++ b/misinformationui/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/misinformationui/macos/.gitignore b/misinformationui/macos/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d4e056954a940c3833d812eaadbd1fac28a2b82e --- /dev/null +++ b/misinformationui/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/misinformationui/macos/Flutter/Flutter-Debug.xcconfig b/misinformationui/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..f022c34e5b87fe6b6bb32fdf9857a928ffa028d7 --- /dev/null +++ b/misinformationui/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/misinformationui/macos/Flutter/Flutter-Release.xcconfig b/misinformationui/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..f022c34e5b87fe6b6bb32fdf9857a928ffa028d7 --- /dev/null +++ b/misinformationui/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/misinformationui/macos/Flutter/GeneratedPluginRegistrant.swift b/misinformationui/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000000000000000000000000000000000000..a1cdfd0cd96db4f3ba97678a27f3cf1daa3c9b10 --- /dev/null +++ b/misinformationui/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import path_provider_foundation +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/misinformationui/macos/Runner.xcodeproj/project.pbxproj b/misinformationui/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000000000000000000000000000000000000..dce4e56fd5aa900ecff03f20e9e14229a427550c --- /dev/null +++ b/misinformationui/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* misinformationui.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "misinformationui.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* misinformationui.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* misinformationui.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/misinformationui.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/misinformationui"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/misinformationui.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/misinformationui"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/misinformationui.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/misinformationui"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/misinformationui/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/misinformationui/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000000000000000000000000000000..fc6bf8074854f8fd9fea8d9cc80d5ca1b8d932db --- /dev/null +++ b/misinformationui/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/misinformationui/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/misinformationui/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..69a0a315b0fb13ab6b1c3643f85b497ffa192c7c --- /dev/null +++ b/misinformationui/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misinformationui/macos/Runner.xcworkspace/contents.xcworkspacedata b/misinformationui/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000000000000000000000000000000000..59c6d39465edc08bd1e919a6248c68b1b72cfd33 --- /dev/null +++ b/misinformationui/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/misinformationui/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/misinformationui/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000000000000000000000000000000..fc6bf8074854f8fd9fea8d9cc80d5ca1b8d932db --- /dev/null +++ b/misinformationui/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/misinformationui/macos/Runner/AppDelegate.swift b/misinformationui/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000000000000000000000000000000000000..c5c474d9235f4c06267e76cccdc98ba2aedd3249 --- /dev/null +++ b/misinformationui/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..8d4e7cb8e7fa82532e515c40051ad7e482256175 --- /dev/null +++ b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..0066c008ba292c746ef170676f5643de1742f589 --- /dev/null +++ b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6232e5815af17e25e0268b2fec7aea9e068cc92ec709e9605c2b31df4ff2a313 +size 102994 diff --git a/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..5dfe938da89613465fa481046638640ba60bbb6f --- /dev/null +++ b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15591f03f31313af6fd644ed0512106cc04365130b8b73244f1cfa6dddfb4400 +size 5680 diff --git a/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000000000000000000000000000000000000..50c377b64308af2f33587a03857b8da38944019b --- /dev/null +++ b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc6928b5adfc00dbf526192e2705dd9af641cdf20ab4c6c7ca7cd4936dca59f0 +size 520 diff --git a/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000000000000000000000000000000000000..045ed5a383f25d0d4ea0ffe99f7525da62d54c04 --- /dev/null +++ b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:416efd77cde932d42ef34168da24dd428a495b1f4f34bbbd125a18d2add186a2 +size 14142 diff --git a/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000000000000000000000000000000000000..9fc6a25dd24671b4411c7202e5e17f3602665160 --- /dev/null +++ b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae8c4458e41f1e28b1e851ed87d3268d4a0351ceea427fa2a84cc94ddfb6d4c5 +size 1066 diff --git a/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000000000000000000000000000000000000..ebe59bef45b2a2bd3abb9648208897fe8090a36e --- /dev/null +++ b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a91c6c1bf242e54ee179e34629e9ef3e8a6d286c0fce01e302280a8be9277e6 +size 36406 diff --git a/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000000000000000000000000000000000000..3f3dd63f18716f1ed06d0239c18e0498f011c44d --- /dev/null +++ b/misinformationui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ad229623498e5f1277800db3ab7cb11faf85eb2569e1213f7d8e55003c07b42 +size 2218 diff --git a/misinformationui/macos/Runner/Base.lproj/MainMenu.xib b/misinformationui/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000000000000000000000000000000000000..4632c6967f03841f921da7c757f2b37c97471054 --- /dev/null +++ b/misinformationui/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misinformationui/macos/Runner/Configs/AppInfo.xcconfig b/misinformationui/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..1910fde7979234f6720b434075cc834241eee523 --- /dev/null +++ b/misinformationui/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = misinformationui + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.misinformationui + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/misinformationui/macos/Runner/Configs/Debug.xcconfig b/misinformationui/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..b3988237237176c5c80b9f06dcecda471b32da64 --- /dev/null +++ b/misinformationui/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/misinformationui/macos/Runner/Configs/Release.xcconfig b/misinformationui/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..d93e5dc4ae5d6583b5562588bd6297eec851ac59 --- /dev/null +++ b/misinformationui/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/misinformationui/macos/Runner/Configs/Warnings.xcconfig b/misinformationui/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..fb4d7d3fb5a2e71bac84a05dbd8c1d0ae1d3b827 --- /dev/null +++ b/misinformationui/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/misinformationui/macos/Runner/DebugProfile.entitlements b/misinformationui/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000000000000000000000000000000000000..51d096708dc10772d5a8c149a432f53a306c9a0b --- /dev/null +++ b/misinformationui/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/misinformationui/macos/Runner/Info.plist b/misinformationui/macos/Runner/Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..3733c1a8eea33dd8914531934be543badf8c1350 --- /dev/null +++ b/misinformationui/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/misinformationui/macos/Runner/MainFlutterWindow.swift b/misinformationui/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000000000000000000000000000000000000..ab30cba82cc767f3f490e642e8e744e10e7550ed --- /dev/null +++ b/misinformationui/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/misinformationui/macos/Runner/Release.entitlements b/misinformationui/macos/Runner/Release.entitlements new file mode 100644 index 0000000000000000000000000000000000000000..04336df3c19b88aa730e073f7a8c43225258dbff --- /dev/null +++ b/misinformationui/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/misinformationui/macos/RunnerTests/RunnerTests.swift b/misinformationui/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..21fe1ab9d96b95e93539a11deb5610c4a9d25b3c --- /dev/null +++ b/misinformationui/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/misinformationui/pubspec.lock b/misinformationui/pubspec.lock new file mode 100644 index 0000000000000000000000000000000000000000..e0be37cd6bd2bf005629569bca795c7f160c9bb6 --- /dev/null +++ b/misinformationui/pubspec.lock @@ -0,0 +1,426 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_linkify: + dependency: "direct main" + description: + name: flutter_linkify + sha256: "74669e06a8f358fee4512b4320c0b80e51cffc496607931de68d28f099254073" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: e20ff62b158b96f392bfc8afe29dee1503c94fbea2cbe8186fd59b756b8ae982 + url: "https://pub.dev" + source: hosted + version: "5.1.0" + http: + dependency: "direct main" + description: + name: http + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + url: "https://pub.dev" + source: hosted + version: "1.5.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + linkify: + dependency: transitive + description: + name: linkify + sha256: "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + lints: + dependency: transitive + description: + name: lints + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + url: "https://pub.dev" + source: hosted + version: "2.2.15" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.dev" + source: hosted + version: "0.7.6" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + url: "https://pub.dev" + source: hosted + version: "6.3.14" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + url: "https://pub.dev" + source: hosted + version: "6.3.3" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.8.0-0 <4.0.0" + flutter: ">=3.24.0" diff --git a/misinformationui/pubspec.yaml b/misinformationui/pubspec.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d185ac0f564aeacb147f197a0f1bd50cfe051a4b --- /dev/null +++ b/misinformationui/pubspec.yaml @@ -0,0 +1,94 @@ +name: misinformationui +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=3.0.0 <4.0.0' + flutter: '>=3.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + flutter_linkify: ^6.0.0 + url_launcher: ^6.3.0 + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + http: ^1.2.1 + google_fonts: ^5.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/misinformationui/static/front.html b/misinformationui/static/front.html new file mode 100644 index 0000000000000000000000000000000000000000..7c844ef0c6d72a16450198fce126c52f61a37c41 --- /dev/null +++ b/misinformationui/static/front.html @@ -0,0 +1,25 @@ + + + + + + Fake News Detection Chatbot + + + + +
+ +
+ + +
+ +
+

Hey,

+

Discover misinformations around you,

+
+ + + + diff --git a/misinformationui/static/main.py b/misinformationui/static/main.py new file mode 100644 index 0000000000000000000000000000000000000000..76e48eb4059b7f3905b2cb9987a1c9a14db76bd9 --- /dev/null +++ b/misinformationui/static/main.py @@ -0,0 +1,327 @@ +#!/home/tom/miniconda3/envs/fake_news_detection/bin/python +""" +main.py - Server for the Fake News Detection system + +This script creates a Flask server that exposes API endpoints to: +1. Take user input (news query) from the UI +2. Process the request through the fake news detection pipeline +3. Return the results to the UI for display +""" + +import os +import json +import time +from dotenv import load_dotenv +from flask import Flask, request, jsonify +from flask_cors import CORS + +# Import required functions from modules +from gdelt_api import ( + fetch_articles_from_gdelt, + filter_by_whitelisted_domains, + normalize_gdelt_articles +) +from ranker import ArticleRanker +from gdelt_query_builder import generate_query, GEMINI_MODEL +import bias_analyzer + +# Global variable for embedding model caching across requests +print("Preloading embedding model for faster request processing...") +# Preload the embedding model at server startup +global_ranker = ArticleRanker() + + +# The function has been removed since bias category descriptions are provided directly by the Gemini model +# and stored in the bias_analysis["descriptions"] dictionary + + +def format_results(query, ranked_articles): + """ + Format the ranked results in a structured way for the UI. + + Args: + query (str): The original query + ranked_articles (list): List of ranked article dictionaries + + Returns: + dict: Dictionary with formatted results + """ + result = {} + + if not ranked_articles: + result = { + "status": "no_results", + "message": "⚠️ No news found. Possibly Fake.", + "details": "No reliable sources could verify this information.", + "articles": [] + } + else: + # Get display configuration from environment variables + show_scores = os.getenv('SHOW_SIMILARITY_SCORES', 'true').lower() == 'true' + show_date = os.getenv('SHOW_PUBLISH_DATE', 'true').lower() == 'true' + show_url = os.getenv('SHOW_URL', 'true').lower() == 'true' + + formatted_articles = [] + for article in ranked_articles: + formatted_article = { + "rank": article['rank'], + "title": article['title'], + "source": article['source'] + } + + if show_scores: + formatted_article["similarity_score"] = round(article['similarity_score'], 4) + + if show_url: + formatted_article["url"] = article['url'] + + if show_date: + formatted_article["published_at"] = article['published_at'] + + formatted_articles.append(formatted_article) + + result = { + "status": "success", + "message": f"✅ Found {len(ranked_articles)} relevant articles for: '{query}'", + "articles": formatted_articles, + "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." + } + + return result + + +def remove_duplicates(articles): + """ + Remove duplicate articles based on URL. + + Args: + articles (list): List of article dictionaries + + Returns: + list: List with duplicate articles removed + """ + unique_urls = set() + unique_articles = [] + + for article in articles: + if article['url'] not in unique_urls: + unique_urls.add(article['url']) + unique_articles.append(article) + + return unique_articles + + +# This function has been removed since Gemini is a cloud API service +# that does not require local caching - models are instantiated as needed + + +def main(): + """Main function to run the fake news detection pipeline as a server.""" + # Load environment variables + load_dotenv() + + # Create Flask app + app = Flask(__name__, static_folder='static') + CORS(app) # Enable CORS for all routes + + @app.route('/static/') + def index(): + """Serve the main page.""" + return app.send_static_file('front.html') + + + @app.route('/api/detect', methods=['POST']) + def detect_fake_news(): + """API endpoint to check if news is potentially fake.""" + # Start timing the request processing + start_time = time.time() + + data = request.json + query = data.get('query', '') + + if not query: + return jsonify({ + "status": "error", + "message": "Please provide a news statement to verify." + }) + + # ===================================================== + # 1. Input Handling + # ===================================================== + # Generate three variations of the query using Gemini + query_variations = generate_query(query) + + # Check if the query was flagged as inappropriate + if query_variations == ["INAPPROPRIATE_QUERY"]: + return jsonify({ + "status": "error", + "message": "I cannot provide information on this topic as it appears to contain sensitive or inappropriate content." + }) + + # ===================================================== + # 2. Data Fetching + # ===================================================== + # Fetch articles from GDELT API for each query variation + all_articles = [] + for query_var in query_variations: + articles = fetch_articles_from_gdelt(query_var) + if articles: + all_articles.extend(articles) + + # Store unique articles in a set to ensure uniqueness + unique_articles = remove_duplicates(all_articles) + + # Apply domain whitelist filtering if enabled in .env + use_whitelist_only = os.getenv('USE_WHITELIST_ONLY', 'false').lower() == 'true' + if use_whitelist_only: + print(f"Filtering articles to only include whitelisted domains...") + unique_articles = filter_by_whitelisted_domains(unique_articles) + print(f"After whitelist filtering: {len(unique_articles)} articles remain") + + # Normalize the articles to a standard format + normalized_articles = normalize_gdelt_articles(unique_articles) + + if not normalized_articles: + return jsonify(format_results(query, [])) + + # ===================================================== + # 3. Embedding & Ranking + # ===================================================== + # Initialize the ranker with model from environment variable + model_name = os.getenv('SIMILARITY_MODEL', 'intfloat/multilingual-e5-base') + + # Use global ranker if it matches the requested model, otherwise create a new instance + if global_ranker.model_name == model_name: + ranker = global_ranker + else: + ranker = ArticleRanker(model_name) + + # Get TOP_K_ARTICLES from .env file + TOP_K_ARTICLES = int(os.getenv('TOP_K_ARTICLES', 250)) + min_threshold = float(os.getenv('MIN_SIMILARITY_THRESHOLD', 0.1)) + + # Prepare article texts for embedding + article_texts = [f"{article['title']} {article['description'] or ''}" for article in normalized_articles] + + # Create embeddings and calculate similarities + query_embedding, article_embeddings = ranker.create_embeddings(query, article_texts) + similarities = ranker.calculate_similarities(query_embedding, article_embeddings) + + # Get top articles based on similarity + top_indices = ranker.get_top_articles(similarities, normalized_articles, TOP_K_ARTICLES, min_threshold) + top_articles = ranker.format_results(top_indices, similarities, normalized_articles) + + # ===================================================== + # 4. Bias Categorization + # ===================================================== + # Extract outlet names from the TOP_K_ARTICLES + # In top_articles, the source is already extracted as a string + outlet_names = [article['source'] for article in top_articles] + unique_outlets = list(set(outlet_names)) + print(f"Analyzing {len(unique_outlets)} unique news outlets for bias...") + + # Analyze bias using Gemini - send just the outlet names, not the whole articles + bias_analysis = bias_analyzer.analyze_bias(query, unique_outlets, GEMINI_MODEL) + + # ===================================================== + # 5. Category Embeddings + # ===================================================== + print("\n" + "=" * 80) + print("EMBEDDING VECTORS BY BIAS CATEGORY") + print("=" * 80) + + # Create embedding vectors for each bias category + # 1. Group articles based on their outlet's bias category + # 2. Create an embedding vector for each category using ONLY article titles + # 3. Rank articles within each category by similarity to query + category_rankings = bias_analyzer.categorize_and_rank_by_bias( + query, normalized_articles, bias_analysis, ranker, min_threshold + ) + + # ===================================================== + # 6. Top N Selection per Category + # ===================================================== + # Get TOP_N_PER_CATEGORY from .env file (default: 5) + TOP_N_PER_CATEGORY = int(os.getenv('TOP_N_PER_CATEGORY', 5)) + + # Get total counts of articles per category before filtering + category_article_counts = { + category: len(articles) + for category, articles in category_rankings.items() + if category not in ["descriptions", "reasoning"] + } + + # For each bias category, select the top N articles + # These are the most relevant articles within each bias perspective + filtered_category_rankings = {} + for category, articles in category_rankings.items(): + # Skip non-category keys like "descriptions" or "reasoning" + if category in ["descriptions", "reasoning"]: + continue + + filtered_category_rankings[category] = articles[:TOP_N_PER_CATEGORY] + + # Only print if there are articles in this category + if len(filtered_category_rankings[category]) > 0: + print(f"\n===== Top {len(filtered_category_rankings[category])} articles from {category} category =====") + + # Print detailed information about each selected article + for i, article in enumerate(filtered_category_rankings[category], 1): + print(f"Article #{i}:") + print(f" Title: {article['title']}") + print(f" Source: {article['source']}") + print(f" Similarity Score: {article['similarity_score']:.4f}") + print(f" Rank: {article['rank']}") + print(f" URL: {article['url']}") + print(f" Published: {article['published_at']}") + print("-" * 50) + + # ===================================================== + # 7. Summarization + # ===================================================== + # Generate summary from articles in all categories + print("\nGenerating factual summary using top articles from all categories...") + + # Pass the original bias_analysis to include the reasoning in the summary + # We need to add the reasoning to filtered_category_rankings since that's what gets passed to generate_summary + filtered_category_rankings["reasoning"] = bias_analysis.get("reasoning", "No reasoning provided") + + # Call the bias_analyzer's generate_summary function with articles from all categories + summary = bias_analyzer.generate_summary( + query, + normalized_articles, + filtered_category_rankings, + GEMINI_MODEL + ) + + # Print the summary to terminal (already includes its own formatting) + print(summary) + + # Prepare response with only the summary and reasoning + result = { + "query": query, + "summary": summary, + "reasoning": bias_analysis.get("reasoning", "No reasoning provided") + } + + return jsonify(result) + + @app.route('/api/health', methods=['GET']) + def health_check(): + """API endpoint to check if the server is running.""" + return jsonify({ + "status": "ok", + "message": "Fake News Detection API is running" + }) + + # Get port from environment variable or use default 5000 + port = int(os.getenv('PORT', 5000)) + debug = os.getenv('DEBUG', 'false').lower() == 'true' + + print(f"Starting Fake News Detection API server on port {port}...") + # Start the Flask server + app.run(host='0.0.0.0', port=port, debug=debug) + + +if __name__ == "__main__": + main() diff --git a/misinformationui/static/script.js b/misinformationui/static/script.js new file mode 100644 index 0000000000000000000000000000000000000000..08d3e49da4b5265c8b303cddf8e12526c9e17f82 --- /dev/null +++ b/misinformationui/static/script.js @@ -0,0 +1,88 @@ +const chat = document.getElementById('chat'); +const input = document.getElementById('query'); +const sendBtn = document.getElementById('sendBtn'); + +function addMessage(text, sender, isPre = false) { + const msg = document.createElement('div'); + msg.classList.add('message', sender); + + // Add special class for pre-formatted messages to style them properly + if (isPre) { + msg.classList.add('pre-formatted'); + + // For pre-formatted text (terminal output) + const pre = document.createElement('pre'); + pre.textContent = text; + + // No inline styles - all styling comes from CSS + msg.appendChild(pre); + } else { + msg.textContent = text; + } + + chat.appendChild(msg); + + // Smooth scroll to new message + setTimeout(() => { + msg.scrollIntoView({ behavior: 'smooth', block: 'end' }); + }, 100); + + return msg; +} + +async function sendMessage() { + const query = input.value.trim(); + if (!query) return; + + const bg = document.getElementById('chat-background'); + if (bg && !bg.classList.contains('blurred')) { + bg.classList.add('blurred'); + } + + addMessage(query, 'user'); + input.value = ''; + + const loader = addMessage('Processing...', 'bot'); + + try { + const response = await fetch('/api/detect', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query: query }) + }); + + const data = await response.json(); + loader.remove(); + + if (data && data.summary) { + // Display summary exactly as it comes from the backend + addMessage(data.summary, 'bot', true); // scrollable
 block
+    } else {
+      addMessage("Could not generate a summary.", 'bot');
+    }
+  } catch (e) {
+    loader.remove();
+    addMessage("Error checking news.", 'bot');
+  }
+}
+
+function formatBackendData(data) {
+  // If we have a summary, only display that
+  if (data && data.summary) {
+    if (typeof data.summary === 'string') {
+      return data.summary;
+    } else if (typeof data.summary === 'object' && data.summary.text) {
+      return data.summary.text;
+    } else {
+      return JSON.stringify(data.summary, null, 2);
+    }
+  }
+  
+  // If no summary is available, return null so we can fall back to showing basic results
+  return null;
+}
+
+sendBtn.addEventListener('click', sendMessage);
+input.addEventListener('keypress', (e) => {
+  if (e.key === 'Enter') sendMessage();
+});
diff --git a/misinformationui/static/style.css b/misinformationui/static/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..b623a875969140545cef4e6cb81161a4c59c15b8
--- /dev/null
+++ b/misinformationui/static/style.css
@@ -0,0 +1,188 @@
+body {
+  font-family: Arial, sans-serif;
+  margin: 0;
+  padding: 0;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+/* Blurred background image */
+body::before {
+  content: "";
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  z-index: -1;
+  background: rgb(23, 23, 23);
+}
+
+.chat-container {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  padding: 15px;
+  overflow-y: auto;
+  overflow-x: hidden; /* prevent horizontal scrolling */
+  margin-bottom: 70px;
+  background: transparent;
+  width: 100%;
+  max-width: 95%;     /* wider to accommodate terminal output */
+  margin-left: auto;   /* center align */
+  margin-right: auto;  /* center align */
+  scroll-behavior: smooth; /* Smooth scrolling */
+  height: calc(100vh - 70px); /* Full height minus input area */
+}
+
+.message {
+  width: fit-content;   /* shrink to text */
+  max-width: 100%;      /* allow full width for terminal output */
+  margin-bottom: 12px;
+  padding: 12px 15px;
+  border-radius: 15px;
+  line-height: 1.4;
+}
+
+/* Special styling for bot messages with pre-formatted text */
+.message.bot.pre-formatted {
+  width: 100%;          /* full width for terminal output */
+  max-width: 100%;      /* no width restriction */
+  white-space: pre-wrap; /* wrap text to prevent horizontal scroll */
+  overflow-wrap: break-word; /* break long words if needed */
+}
+
+.user {
+  align-self: flex-end;   /* right side */
+  background: #414141;
+  color: #fff;
+  border-bottom-right-radius: 5px;
+}
+
+.bot {
+  align-self: flex-start; /* left side */
+  background: transparent;
+  color: #ffffff;
+  border-bottom-left-radius: 5px;
+}
+
+/* Terminal output style - matching exactly what appears in the terminal */
+.message.bot pre {
+  font-family: monospace;
+  background-color: transparent; /* No background color */
+  color: inherit; /* Use the same text color as the parent */
+  padding: 0;
+  border: none;
+  width: 100%;
+  max-height: none; /* No height limit */
+  overflow-x: visible; /* No horizontal scrolling */
+  white-space: pre-wrap; /* Wrap text to prevent horizontal scrolling */
+  font-size: inherit;
+  line-height: 1.4;
+}
+
+
+
+.input-area {
+  position: fixed;
+  bottom: 1rem;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 100%;
+  max-width: 95%; /* Match the width of chat container */
+  display: flex;
+  padding: 10px;
+  background: rgba(54, 54, 54, 0.7); /* make input area semi-transparent */
+  box-shadow: 0 -2px 5px rgba(0,0,0,0.05);
+  border-radius: 30px;
+  z-index: 10; /* Ensure input stays on top */
+}
+
+.input-area input {
+  flex: 1;
+  padding: 14px 18px;
+  border: 1px solid #1b1b1b;
+  box-shadow: #414141;
+  border-radius: 25px;
+  outline: none;
+  font-size: 1rem;
+}
+
+.input-area button {
+  margin-left: 10px;
+  padding: 0 20px;
+  border: none;
+  background: #1f1f1f;
+  color: white;
+  border-radius: 25px;
+  cursor: pointer;
+  font-size: 1rem;
+  transition: background 0.2s ease;
+}
+
+.input-area button:hover {
+  background: #6a6a6a;
+}
+
+.loader {
+  font-size: 0.9rem;
+  color: gray;
+  margin: 5px 0;
+}
+.chat-background{
+  position: fixed;
+  font-family: 'Courier New', Courier, monospace;
+  top: 40%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  font-weight: bold;
+  color: rgba(255, 255, 255, 0.8);  /* semi-transparent */
+  text-align: center;
+  z-index: 0;  /* below chat messages */
+  pointer-events: none; /* makes it "untouchable" */
+  transition: all 0.4s ease;
+}
+.chat-background{
+  display: inline-block;
+  text-align: left;
+}
+.chat-background #p1 {
+  font-size: 4rem;
+}
+
+.chat-background #p2 {
+  font-size: 3rem;
+}
+
+/* When blurred */
+.chat-background.blurred {
+  filter: blur(12px); /* strong blur */
+  opacity: 0.4; /* fade slightly for readability */
+  transition: all 0.4s ease;
+}
+
+@media (max-width: 600px) {
+  .message {
+    max-width: 85%;
+  }
+  
+  .chat-container {
+    padding: 10px;
+    max-width: 100%;
+  }
+  
+  .input-area {
+    max-width: 95%;
+    bottom: 0.5rem;
+  }
+  
+  .chat-background #p1 {
+    font-size: 3rem;
+  }
+  
+  .chat-background #p2 {
+    font-size: 2rem;
+  }
+}
diff --git a/misinformationui/static/test_server.py b/misinformationui/static/test_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/misinformationui/test/widget_test.dart b/misinformationui/test/widget_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..261fc87afcefc05db8b19674be1c7ca920ced20f
--- /dev/null
+++ b/misinformationui/test/widget_test.dart
@@ -0,0 +1,30 @@
+// This is a basic Flutter widget test.
+//
+// To perform an interaction with a widget in your test, use the WidgetTester
+// utility in the flutter_test package. For example, you can send tap and scroll
+// gestures. You can also use WidgetTester to find child widgets in the widget
+// tree, read text, and verify that the values of widget properties are correct.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:misinformationui/main.dart';
+
+void main() {
+  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
+    // Build our app and trigger a frame.
+    await tester.pumpWidget(const MyApp());
+
+    // Verify that our counter starts at 0.
+    expect(find.text('0'), findsOneWidget);
+    expect(find.text('1'), findsNothing);
+
+    // Tap the '+' icon and trigger a frame.
+    await tester.tap(find.byIcon(Icons.add));
+    await tester.pump();
+
+    // Verify that our counter has incremented.
+    expect(find.text('0'), findsNothing);
+    expect(find.text('1'), findsOneWidget);
+  });
+}
diff --git a/misinformationui/web/favicon.png b/misinformationui/web/favicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..789da555426dce34be1aa66386f39b9482f7284c
--- /dev/null
+++ b/misinformationui/web/favicon.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7ab2525f4b86b65d3e4c70358a17e5a1aaf6f437f99cbcc046dad73d59bb9015
+size 917
diff --git a/misinformationui/web/icons/Icon-192.png b/misinformationui/web/icons/Icon-192.png
new file mode 100644
index 0000000000000000000000000000000000000000..ea18c9895f624b678c5bfd164be1a56d1d7337e8
--- /dev/null
+++ b/misinformationui/web/icons/Icon-192.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3dce99077602f70421c1c6b2a240bc9b83d64d86681d45f2154143310c980be3
+size 5292
diff --git a/misinformationui/web/icons/Icon-512.png b/misinformationui/web/icons/Icon-512.png
new file mode 100644
index 0000000000000000000000000000000000000000..c2bd3920740c4533560930a1c552015c69efef2a
--- /dev/null
+++ b/misinformationui/web/icons/Icon-512.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:baccb205ae45f0b421be1657259b4943ac40c95094ab877f3bcbe12cd544dcbe
+size 8252
diff --git a/misinformationui/web/icons/Icon-maskable-192.png b/misinformationui/web/icons/Icon-maskable-192.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c63e0c4894ad2ddba48d332b04e0f0a713547c1
--- /dev/null
+++ b/misinformationui/web/icons/Icon-maskable-192.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d2c842e22a9f4ec9d996b23373a905c88d9a203b220c5c151885ad621f974b5c
+size 5594
diff --git a/misinformationui/web/icons/Icon-maskable-512.png b/misinformationui/web/icons/Icon-maskable-512.png
new file mode 100644
index 0000000000000000000000000000000000000000..2e7131f2098587f151de196b25656e2e303a14e1
--- /dev/null
+++ b/misinformationui/web/icons/Icon-maskable-512.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6aee06cdcab6b2aef74b1734c4778f4421d2da100b0ff9e52b21b55240202929
+size 20998
diff --git a/misinformationui/web/index.html b/misinformationui/web/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..9b82be99127ef66bfe995ea0fe6dd241502a197b
--- /dev/null
+++ b/misinformationui/web/index.html
@@ -0,0 +1,38 @@
+
+
+
+  
+  
+
+  
+  
+  
+
+  
+  
+  
+  
+  
+
+  
+  
+
+  misinformationui
+  
+
+
+  
+
+
diff --git a/misinformationui/web/manifest.json b/misinformationui/web/manifest.json
new file mode 100644
index 0000000000000000000000000000000000000000..2de6afc5c43cd47c3f147a30fe7b917b8a294db5
--- /dev/null
+++ b/misinformationui/web/manifest.json
@@ -0,0 +1,35 @@
+{
+    "name": "misinformationui",
+    "short_name": "misinformationui",
+    "start_url": ".",
+    "display": "standalone",
+    "background_color": "#0175C2",
+    "theme_color": "#0175C2",
+    "description": "A new Flutter project.",
+    "orientation": "portrait-primary",
+    "prefer_related_applications": false,
+    "icons": [
+        {
+            "src": "icons/Icon-192.png",
+            "sizes": "192x192",
+            "type": "image/png"
+        },
+        {
+            "src": "icons/Icon-512.png",
+            "sizes": "512x512",
+            "type": "image/png"
+        },
+        {
+            "src": "icons/Icon-maskable-192.png",
+            "sizes": "192x192",
+            "type": "image/png",
+            "purpose": "maskable"
+        },
+        {
+            "src": "icons/Icon-maskable-512.png",
+            "sizes": "512x512",
+            "type": "image/png",
+            "purpose": "maskable"
+        }
+    ]
+}
diff --git a/misinformationui/windows/.gitignore b/misinformationui/windows/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..ec4098aa65acfc141a8dde49a52563985fe738c6
--- /dev/null
+++ b/misinformationui/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/misinformationui/windows/CMakeLists.txt b/misinformationui/windows/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d27b4419c0b3bc6a2c92d6ff4c9136face6dc555
--- /dev/null
+++ b/misinformationui/windows/CMakeLists.txt
@@ -0,0 +1,108 @@
+# Project-level configuration.
+cmake_minimum_required(VERSION 3.14)
+project(misinformationui LANGUAGES CXX)
+
+# The name of the executable created for the application. Change this to change
+# the on-disk name of your application.
+set(BINARY_NAME "misinformationui")
+
+# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
+# versions of CMake.
+cmake_policy(VERSION 3.14...3.25)
+
+# Define build configuration option.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+    CACHE STRING "" FORCE)
+else()
+  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+    set(CMAKE_BUILD_TYPE "Debug" CACHE
+      STRING "Flutter build mode" FORCE)
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+      "Debug" "Profile" "Release")
+  endif()
+endif()
+# Define settings for the Profile build mode.
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+#
+# Be cautious about adding new options here, as plugins use this function by
+# default. In most cases, you should add new options to specific targets instead
+# of modifying this function.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_17)
+  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+  target_compile_options(${TARGET} PRIVATE /EHsc)
+  target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+  target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>")
+endfunction()
+
+# Flutter library and tool build rules.
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build; see runner/CMakeLists.txt.
+add_subdirectory("runner")
+
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Copy the native assets provided by the build.dart from all packages.
+set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/")
+install(DIRECTORY "${NATIVE_ASSETS_DIR}"
+   DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+   COMPONENT Runtime)
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  CONFIGURATIONS Profile;Release
+  COMPONENT Runtime)
diff --git a/misinformationui/windows/flutter/CMakeLists.txt b/misinformationui/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..efb62ebe7ddbf1d5961ae854d14fe8d6695d5c99
--- /dev/null
+++ b/misinformationui/windows/flutter/CMakeLists.txt
@@ -0,0 +1,109 @@
+# This file controls Flutter-level build steps. It should not be edited.
+cmake_minimum_required(VERSION 3.14)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# Set fallback configurations for older versions of the flutter tool.
+if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
+  set(FLUTTER_TARGET_PLATFORM "windows-x64")
+endif()
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "flutter_export.h"
+  "flutter_windows.h"
+  "flutter_messenger.h"
+  "flutter_plugin_registrar.h"
+  "flutter_texture_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+  "core_implementations.cc"
+  "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+  "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+  "flutter_engine.cc"
+  "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+    ${CPP_WRAPPER_SOURCES_APP}
+    ${PHONY_OUTPUT}
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+      ${FLUTTER_TARGET_PLATFORM} $
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/misinformationui/windows/flutter/generated_plugin_registrant.cc b/misinformationui/windows/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000000000000000000000000000000000000..4f7884874da74281c7ffe7fa4b936e7b2af92c24
--- /dev/null
+++ b/misinformationui/windows/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,14 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#include "generated_plugin_registrant.h"
+
+#include 
+
+void RegisterPlugins(flutter::PluginRegistry* registry) {
+  UrlLauncherWindowsRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("UrlLauncherWindows"));
+}
diff --git a/misinformationui/windows/flutter/generated_plugin_registrant.h b/misinformationui/windows/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000000000000000000000000000000000000..dc139d85a93101cc0f6e9db03a3e1a9f68e8dd7e
--- /dev/null
+++ b/misinformationui/windows/flutter/generated_plugin_registrant.h
@@ -0,0 +1,15 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include 
+
+// Registers Flutter plugins.
+void RegisterPlugins(flutter::PluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_
diff --git a/misinformationui/windows/flutter/generated_plugins.cmake b/misinformationui/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..88b22e5c775e5e83b1bb9140b0d1c0c38c2871a0
--- /dev/null
+++ b/misinformationui/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,24 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+  url_launcher_windows
+)
+
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)
diff --git a/misinformationui/windows/runner/CMakeLists.txt b/misinformationui/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2041a0441082ae7295179dd095457caecb7b3842
--- /dev/null
+++ b/misinformationui/windows/runner/CMakeLists.txt
@@ -0,0 +1,40 @@
+cmake_minimum_required(VERSION 3.14)
+project(runner LANGUAGES CXX)
+
+# Define the application target. To change its name, change BINARY_NAME in the
+# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
+# work.
+#
+# Any new source files that you add to the application should be added here.
+add_executable(${BINARY_NAME} WIN32
+  "flutter_window.cpp"
+  "main.cpp"
+  "utils.cpp"
+  "win32_window.cpp"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+  "Runner.rc"
+  "runner.exe.manifest"
+)
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
+apply_standard_settings(${BINARY_NAME})
+
+# Add preprocessor definitions for the build version.
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
+
+# Disable Windows macros that collide with C++ standard library functions.
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+
+# Add dependency libraries and include directories. Add any application-specific
+# dependencies here.
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+
+# Run the Flutter tool portions of the build. This must not be removed.
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/misinformationui/windows/runner/Runner.rc b/misinformationui/windows/runner/Runner.rc
new file mode 100644
index 0000000000000000000000000000000000000000..c89722f2382e4c348fcc31b841140a2ec1167583
--- /dev/null
+++ b/misinformationui/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+    "#include ""winres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON            ICON                    "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
+#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
+#else
+#define VERSION_AS_NUMBER 1,0,0,0
+#endif
+
+#if defined(FLUTTER_VERSION)
+#define VERSION_AS_STRING FLUTTER_VERSION
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "com.example" "\0"
+            VALUE "FileDescription", "misinformationui" "\0"
+            VALUE "FileVersion", VERSION_AS_STRING "\0"
+            VALUE "InternalName", "misinformationui" "\0"
+            VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0"
+            VALUE "OriginalFilename", "misinformationui.exe" "\0"
+            VALUE "ProductName", "misinformationui" "\0"
+            VALUE "ProductVersion", VERSION_AS_STRING "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
+
+#endif    // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
diff --git a/misinformationui/windows/runner/flutter_window.cpp b/misinformationui/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c819cb083f2cbe0ff97e661bfb885b114792b38c
--- /dev/null
+++ b/misinformationui/windows/runner/flutter_window.cpp
@@ -0,0 +1,71 @@
+#include "flutter_window.h"
+
+#include 
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(const flutter::DartProject& project)
+    : project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+  if (!Win32Window::OnCreate()) {
+    return false;
+  }
+
+  RECT frame = GetClientArea();
+
+  // The size here must match the window dimensions to avoid unnecessary surface
+  // creation / destruction in the startup path.
+  flutter_controller_ = std::make_unique(
+      frame.right - frame.left, frame.bottom - frame.top, project_);
+  // Ensure that basic setup of the controller was successful.
+  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+    return false;
+  }
+  RegisterPlugins(flutter_controller_->engine());
+  SetChildContent(flutter_controller_->view()->GetNativeWindow());
+
+  flutter_controller_->engine()->SetNextFrameCallback([&]() {
+    this->Show();
+  });
+
+  // Flutter can complete the first frame before the "show window" callback is
+  // registered. The following call ensures a frame is pending to ensure the
+  // window is shown. It is a no-op if the first frame hasn't completed yet.
+  flutter_controller_->ForceRedraw();
+
+  return true;
+}
+
+void FlutterWindow::OnDestroy() {
+  if (flutter_controller_) {
+    flutter_controller_ = nullptr;
+  }
+
+  Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+                              WPARAM const wparam,
+                              LPARAM const lparam) noexcept {
+  // Give Flutter, including plugins, an opportunity to handle window messages.
+  if (flutter_controller_) {
+    std::optional result =
+        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+                                                      lparam);
+    if (result) {
+      return *result;
+    }
+  }
+
+  switch (message) {
+    case WM_FONTCHANGE:
+      flutter_controller_->engine()->ReloadSystemFonts();
+      break;
+  }
+
+  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/misinformationui/windows/runner/flutter_window.h b/misinformationui/windows/runner/flutter_window.h
new file mode 100644
index 0000000000000000000000000000000000000000..28c23839b96e0dbf6238372ae04fc46044e670b8
--- /dev/null
+++ b/misinformationui/windows/runner/flutter_window.h
@@ -0,0 +1,33 @@
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include 
+#include 
+
+#include 
+
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+  // Creates a new FlutterWindow hosting a Flutter view running |project|.
+  explicit FlutterWindow(const flutter::DartProject& project);
+  virtual ~FlutterWindow();
+
+ protected:
+  // Win32Window:
+  bool OnCreate() override;
+  void OnDestroy() override;
+  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+                         LPARAM const lparam) noexcept override;
+
+ private:
+  // The project to run.
+  flutter::DartProject project_;
+
+  // The Flutter instance hosted by this window.
+  std::unique_ptr flutter_controller_;
+};
+
+#endif  // RUNNER_FLUTTER_WINDOW_H_
diff --git a/misinformationui/windows/runner/main.cpp b/misinformationui/windows/runner/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b9c898210da0cba10100273aee9871cc08c772aa
--- /dev/null
+++ b/misinformationui/windows/runner/main.cpp
@@ -0,0 +1,43 @@
+#include 
+#include 
+#include 
+
+#include "flutter_window.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+                      _In_ wchar_t *command_line, _In_ int show_command) {
+  // Attach to console when present (e.g., 'flutter run') or create a
+  // new console when running with a debugger.
+  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+    CreateAndAttachConsole();
+  }
+
+  // Initialize COM, so that it is available for use in the library and/or
+  // plugins.
+  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+  flutter::DartProject project(L"data");
+
+  std::vector command_line_arguments =
+      GetCommandLineArguments();
+
+  project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
+
+  FlutterWindow window(project);
+  Win32Window::Point origin(10, 10);
+  Win32Window::Size size(1280, 720);
+  if (!window.Create(L"misinformationui", origin, size)) {
+    return EXIT_FAILURE;
+  }
+  window.SetQuitOnClose(true);
+
+  ::MSG msg;
+  while (::GetMessage(&msg, nullptr, 0, 0)) {
+    ::TranslateMessage(&msg);
+    ::DispatchMessage(&msg);
+  }
+
+  ::CoUninitialize();
+  return EXIT_SUCCESS;
+}
diff --git a/misinformationui/windows/runner/resource.h b/misinformationui/windows/runner/resource.h
new file mode 100644
index 0000000000000000000000000000000000000000..ddc7f3efc0e6c820aec21ceccd42537b74645a9a
--- /dev/null
+++ b/misinformationui/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON                    101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE        102
+#define _APS_NEXT_COMMAND_VALUE         40001
+#define _APS_NEXT_CONTROL_VALUE         1001
+#define _APS_NEXT_SYMED_VALUE           101
+#endif
+#endif
diff --git a/misinformationui/windows/runner/resources/app_icon.ico b/misinformationui/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6
Binary files /dev/null and b/misinformationui/windows/runner/resources/app_icon.ico differ
diff --git a/misinformationui/windows/runner/runner.exe.manifest b/misinformationui/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000000000000000000000000000000000000..4b962bbeb71c54552723c41980a5cf20bc9cfdd3
--- /dev/null
+++ b/misinformationui/windows/runner/runner.exe.manifest
@@ -0,0 +1,14 @@
+
+
+  
+    
+      PerMonitorV2
+    
+  
+  
+    
+      
+      
+    
+  
+
diff --git a/misinformationui/windows/runner/utils.cpp b/misinformationui/windows/runner/utils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..259d85bff07a93df668a6ad685f3259033f2313f
--- /dev/null
+++ b/misinformationui/windows/runner/utils.cpp
@@ -0,0 +1,65 @@
+#include "utils.h"
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+void CreateAndAttachConsole() {
+  if (::AllocConsole()) {
+    FILE *unused;
+    if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+      _dup2(_fileno(stdout), 1);
+    }
+    if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+      _dup2(_fileno(stdout), 2);
+    }
+    std::ios::sync_with_stdio();
+    FlutterDesktopResyncOutputStreams();
+  }
+}
+
+std::vector GetCommandLineArguments() {
+  // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
+  int argc;
+  wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
+  if (argv == nullptr) {
+    return std::vector();
+  }
+
+  std::vector command_line_arguments;
+
+  // Skip the first argument as it's the binary name.
+  for (int i = 1; i < argc; i++) {
+    command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
+  }
+
+  ::LocalFree(argv);
+
+  return command_line_arguments;
+}
+
+std::string Utf8FromUtf16(const wchar_t* utf16_string) {
+  if (utf16_string == nullptr) {
+    return std::string();
+  }
+  unsigned int target_length = ::WideCharToMultiByte(
+      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
+      -1, nullptr, 0, nullptr, nullptr)
+    -1; // remove the trailing null character
+  int input_length = (int)wcslen(utf16_string);
+  std::string utf8_string;
+  if (target_length == 0 || target_length > utf8_string.max_size()) {
+    return utf8_string;
+  }
+  utf8_string.resize(target_length);
+  int converted_length = ::WideCharToMultiByte(
+      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
+      input_length, utf8_string.data(), target_length, nullptr, nullptr);
+  if (converted_length == 0) {
+    return std::string();
+  }
+  return utf8_string;
+}
diff --git a/misinformationui/windows/runner/utils.h b/misinformationui/windows/runner/utils.h
new file mode 100644
index 0000000000000000000000000000000000000000..3f0e05cba3cc07d7982c48e0e5a49129d4dddfbb
--- /dev/null
+++ b/misinformationui/windows/runner/utils.h
@@ -0,0 +1,19 @@
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+#include 
+#include 
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
+// encoded in UTF-8. Returns an empty std::string on failure.
+std::string Utf8FromUtf16(const wchar_t* utf16_string);
+
+// Gets the command line arguments passed in as a std::vector,
+// encoded in UTF-8. Returns an empty std::vector on failure.
+std::vector GetCommandLineArguments();
+
+#endif  // RUNNER_UTILS_H_
diff --git a/misinformationui/windows/runner/win32_window.cpp b/misinformationui/windows/runner/win32_window.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b5ba2a099fb8fb39e4638338df01b754f0174a8c
--- /dev/null
+++ b/misinformationui/windows/runner/win32_window.cpp
@@ -0,0 +1,288 @@
+#include "win32_window.h"
+
+#include 
+#include 
+
+#include "resource.h"
+
+namespace {
+
+/// Window attribute that enables dark mode window decorations.
+///
+/// Redefined in case the developer's machine has a Windows SDK older than
+/// version 10.0.22000.0.
+/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
+#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
+#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
+#endif
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+/// Registry key for app theme preference.
+///
+/// A value of 0 indicates apps should use dark mode. A non-zero or missing
+/// value indicates apps should use light mode.
+constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
+  L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
+constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+  return static_cast(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+  HMODULE user32_module = LoadLibraryA("User32.dll");
+  if (!user32_module) {
+    return;
+  }
+  auto enable_non_client_dpi_scaling =
+      reinterpret_cast(
+          GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+  if (enable_non_client_dpi_scaling != nullptr) {
+    enable_non_client_dpi_scaling(hwnd);
+  }
+  FreeLibrary(user32_module);
+}
+
+}  // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+  ~WindowClassRegistrar() = default;
+
+  // Returns the singleton registrar instance.
+  static WindowClassRegistrar* GetInstance() {
+    if (!instance_) {
+      instance_ = new WindowClassRegistrar();
+    }
+    return instance_;
+  }
+
+  // Returns the name of the window class, registering the class if it hasn't
+  // previously been registered.
+  const wchar_t* GetWindowClass();
+
+  // Unregisters the window class. Should only be called if there are no
+  // instances of the window.
+  void UnregisterWindowClass();
+
+ private:
+  WindowClassRegistrar() = default;
+
+  static WindowClassRegistrar* instance_;
+
+  bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+  if (!class_registered_) {
+    WNDCLASS window_class{};
+    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+    window_class.lpszClassName = kWindowClassName;
+    window_class.style = CS_HREDRAW | CS_VREDRAW;
+    window_class.cbClsExtra = 0;
+    window_class.cbWndExtra = 0;
+    window_class.hInstance = GetModuleHandle(nullptr);
+    window_class.hIcon =
+        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+    window_class.hbrBackground = 0;
+    window_class.lpszMenuName = nullptr;
+    window_class.lpfnWndProc = Win32Window::WndProc;
+    RegisterClass(&window_class);
+    class_registered_ = true;
+  }
+  return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+  UnregisterClass(kWindowClassName, nullptr);
+  class_registered_ = false;
+}
+
+Win32Window::Win32Window() {
+  ++g_active_window_count;
+}
+
+Win32Window::~Win32Window() {
+  --g_active_window_count;
+  Destroy();
+}
+
+bool Win32Window::Create(const std::wstring& title,
+                         const Point& origin,
+                         const Size& size) {
+  Destroy();
+
+  const wchar_t* window_class =
+      WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+  const POINT target_point = {static_cast(origin.x),
+                              static_cast(origin.y)};
+  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+  double scale_factor = dpi / 96.0;
+
+  HWND window = CreateWindow(
+      window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
+      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+      Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+      nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+  if (!window) {
+    return false;
+  }
+
+  UpdateTheme(window);
+
+  return OnCreate();
+}
+
+bool Win32Window::Show() {
+  return ShowWindow(window_handle_, SW_SHOWNORMAL);
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window,
+                                      UINT const message,
+                                      WPARAM const wparam,
+                                      LPARAM const lparam) noexcept {
+  if (message == WM_NCCREATE) {
+    auto window_struct = reinterpret_cast(lparam);
+    SetWindowLongPtr(window, GWLP_USERDATA,
+                     reinterpret_cast(window_struct->lpCreateParams));
+
+    auto that = static_cast(window_struct->lpCreateParams);
+    EnableFullDpiSupportIfAvailable(window);
+    that->window_handle_ = window;
+  } else if (Win32Window* that = GetThisFromHandle(window)) {
+    return that->MessageHandler(window, message, wparam, lparam);
+  }
+
+  return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd,
+                            UINT const message,
+                            WPARAM const wparam,
+                            LPARAM const lparam) noexcept {
+  switch (message) {
+    case WM_DESTROY:
+      window_handle_ = nullptr;
+      Destroy();
+      if (quit_on_close_) {
+        PostQuitMessage(0);
+      }
+      return 0;
+
+    case WM_DPICHANGED: {
+      auto newRectSize = reinterpret_cast(lparam);
+      LONG newWidth = newRectSize->right - newRectSize->left;
+      LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+      return 0;
+    }
+    case WM_SIZE: {
+      RECT rect = GetClientArea();
+      if (child_content_ != nullptr) {
+        // Size and position the child window.
+        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+                   rect.bottom - rect.top, TRUE);
+      }
+      return 0;
+    }
+
+    case WM_ACTIVATE:
+      if (child_content_ != nullptr) {
+        SetFocus(child_content_);
+      }
+      return 0;
+
+    case WM_DWMCOLORIZATIONCOLORCHANGED:
+      UpdateTheme(hwnd);
+      return 0;
+  }
+
+  return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+  OnDestroy();
+
+  if (window_handle_) {
+    DestroyWindow(window_handle_);
+    window_handle_ = nullptr;
+  }
+  if (g_active_window_count == 0) {
+    WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+  }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+  return reinterpret_cast(
+      GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+  child_content_ = content;
+  SetParent(content, window_handle_);
+  RECT frame = GetClientArea();
+
+  MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+             frame.bottom - frame.top, true);
+
+  SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+  RECT frame;
+  GetClientRect(window_handle_, &frame);
+  return frame;
+}
+
+HWND Win32Window::GetHandle() {
+  return window_handle_;
+}
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+  quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+  // No-op; provided for subclasses.
+  return true;
+}
+
+void Win32Window::OnDestroy() {
+  // No-op; provided for subclasses.
+}
+
+void Win32Window::UpdateTheme(HWND const window) {
+  DWORD light_mode;
+  DWORD light_mode_size = sizeof(light_mode);
+  LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
+                               kGetPreferredBrightnessRegValue,
+                               RRF_RT_REG_DWORD, nullptr, &light_mode,
+                               &light_mode_size);
+
+  if (result == ERROR_SUCCESS) {
+    BOOL enable_dark_mode = light_mode == 0;
+    DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
+                          &enable_dark_mode, sizeof(enable_dark_mode));
+  }
+}
diff --git a/misinformationui/windows/runner/win32_window.h b/misinformationui/windows/runner/win32_window.h
new file mode 100644
index 0000000000000000000000000000000000000000..49b847f07511cb8fca530a23cf0e711f4bb6e5cf
--- /dev/null
+++ b/misinformationui/windows/runner/win32_window.h
@@ -0,0 +1,102 @@
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include 
+
+#include 
+#include 
+#include 
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+  struct Point {
+    unsigned int x;
+    unsigned int y;
+    Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+  };
+
+  struct Size {
+    unsigned int width;
+    unsigned int height;
+    Size(unsigned int width, unsigned int height)
+        : width(width), height(height) {}
+  };
+
+  Win32Window();
+  virtual ~Win32Window();
+
+  // Creates a win32 window with |title| that is positioned and sized using
+  // |origin| and |size|. New windows are created on the default monitor. Window
+  // sizes are specified to the OS in physical pixels, hence to ensure a
+  // consistent size this function will scale the inputted width and height as
+  // as appropriate for the default monitor. The window is invisible until
+  // |Show| is called. Returns true if the window was created successfully.
+  bool Create(const std::wstring& title, const Point& origin, const Size& size);
+
+  // Show the current window. Returns true if the window was successfully shown.
+  bool Show();
+
+  // Release OS resources associated with window.
+  void Destroy();
+
+  // Inserts |content| into the window tree.
+  void SetChildContent(HWND content);
+
+  // Returns the backing Window handle to enable clients to set icon and other
+  // window properties. Returns nullptr if the window has been destroyed.
+  HWND GetHandle();
+
+  // If true, closing this window will quit the application.
+  void SetQuitOnClose(bool quit_on_close);
+
+  // Return a RECT representing the bounds of the current client area.
+  RECT GetClientArea();
+
+ protected:
+  // Processes and route salient window messages for mouse handling,
+  // size change and DPI. Delegates handling of these to member overloads that
+  // inheriting classes can handle.
+  virtual LRESULT MessageHandler(HWND window,
+                                 UINT const message,
+                                 WPARAM const wparam,
+                                 LPARAM const lparam) noexcept;
+
+  // Called when CreateAndShow is called, allowing subclass window-related
+  // setup. Subclasses should return false if setup fails.
+  virtual bool OnCreate();
+
+  // Called when Destroy is called.
+  virtual void OnDestroy();
+
+ private:
+  friend class WindowClassRegistrar;
+
+  // OS callback called by message pump. Handles the WM_NCCREATE message which
+  // is passed when the non-client area is being created and enables automatic
+  // non-client DPI scaling so that the non-client area automatically
+  // responds to changes in DPI. All other messages are handled by
+  // MessageHandler.
+  static LRESULT CALLBACK WndProc(HWND const window,
+                                  UINT const message,
+                                  WPARAM const wparam,
+                                  LPARAM const lparam) noexcept;
+
+  // Retrieves a class instance pointer for |window|
+  static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+  // Update the window frame's theme to match the system theme.
+  static void UpdateTheme(HWND const window);
+
+  bool quit_on_close_ = false;
+
+  // window handle for top level window.
+  HWND window_handle_ = nullptr;
+
+  // window handle for hosted content.
+  HWND child_content_ = nullptr;
+};
+
+#endif  // RUNNER_WIN32_WINDOW_H_
diff --git a/needed_links b/needed_links
new file mode 100644
index 0000000000000000000000000000000000000000..f05d41141d0a054744e5ad1160d798c4efdec58b
--- /dev/null
+++ b/needed_links
@@ -0,0 +1,2 @@
+https://console.cloud.google.com/
+https://programmablesearchengine.google.com/
diff --git a/ranker.py b/ranker.py
new file mode 100644
index 0000000000000000000000000000000000000000..70eba30b17fb5715fcafae8e0a1d900a6cae42f2
--- /dev/null
+++ b/ranker.py
@@ -0,0 +1,142 @@
+"""
+ranker.py - Module for ranking news articles based on relevance to query
+
+This module uses SentenceTransformers to create embeddings and rank articles
+based on their semantic similarity to the user query.
+"""
+
+import os
+import torch
+from sentence_transformers import SentenceTransformer
+
+# Global model cache dictionary to store loaded models
+_MODEL_CACHE = {}
+
+# Check if CUDA is available
+DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
+print(f"Using device: {DEVICE}")
+
+class ArticleRanker:
+    """A class to rank articles based on their relevance to a query using embeddings."""
+    
+    def __init__(self, model_name=None):
+        """
+        Initialize the ranker with a SentenceTransformer model.
+        
+        Args:
+            model_name (str): Name of the SentenceTransformer model to use
+        """
+        # Get model name or use default
+        self.model_name = model_name or 'intfloat/multilingual-e5-base'
+        
+        try:
+            # Check if model is already in cache
+            if self.model_name in _MODEL_CACHE:
+                self.model = _MODEL_CACHE[self.model_name]
+                print(f"Using cached model: {self.model_name} on {DEVICE}")
+            else:
+                # Load model and add to cache, automatically using the appropriate device
+                self.model = SentenceTransformer(self.model_name, device=str(DEVICE))
+                _MODEL_CACHE[self.model_name] = self.model
+                print(f"Loaded model: {self.model_name} on {DEVICE}")
+        except Exception as e:
+            print(f"⚠️ Error loading model: {e}")
+            print("Make sure you have installed sentence-transformers:")
+            print("pip install sentence-transformers")
+            raise
+    
+    def create_embeddings(self, query, article_texts):
+        """
+        Create embeddings for the query and article texts.
+        
+        Args:
+            query (str): The user's query/news statement
+            article_texts (list): List of article text strings
+            
+        Returns:
+            tuple: (query_embedding, article_embeddings)
+        """
+        # Use convert_to_tensor=True to get tensors that will be on the right device (CUDA or CPU)
+        query_embedding = self.model.encode(query, convert_to_tensor=True, device=DEVICE)
+        article_embeddings = self.model.encode(article_texts, convert_to_tensor=True, device=DEVICE)
+        return query_embedding, article_embeddings
+    
+    def calculate_similarities(self, query_embedding, article_embeddings):
+        """
+        Calculate cosine similarity between query and article embeddings.
+        
+        Args:
+            query_embedding: Tensor of query embedding
+            article_embeddings: Tensor of article embeddings
+            
+        Returns:
+            list: List of similarity scores
+        """
+        similarities = torch.nn.functional.cosine_similarity(
+            query_embedding.unsqueeze(0), article_embeddings
+        )
+        return similarities.tolist()
+    
+    def get_top_articles(self, similarities, articles, top_n, min_threshold):
+        """
+        Get the top N articles based on similarity scores.
+        
+        Args:
+            similarities (list): List of similarity scores
+            articles (list): List of article dictionaries
+            top_n (int): Number of top results to return
+            min_threshold (float): Minimum similarity threshold
+            
+        Returns:
+            list: List of indices of top articles
+        """
+        # Create a list of (index, similarity) tuples and sort by similarity (descending)
+        indexed_similarities = list(enumerate(similarities))
+        indexed_similarities.sort(key=lambda x: x[1], reverse=True)
+        
+        top_indices = []
+        
+        # Filter results by threshold
+        for idx, score in indexed_similarities:
+            if score >= min_threshold and len(top_indices) < top_n:
+                top_indices.append(idx)
+        
+        return top_indices
+    
+    def format_results(self, top_indices, similarities, articles):
+        """
+        Format the results with rank and similarity score.
+        
+        Args:
+            top_indices (list): List of indices of top articles
+            similarities (list): List of similarity scores
+            articles (list): List of article dictionaries
+            
+        Returns:
+            list: List of dictionaries with ranked articles and their similarity scores
+        """
+        result = []
+        
+        for i, idx in enumerate(top_indices, 1):
+            similarity_score = similarities[idx]
+            article = articles[idx]
+            
+            # Extract source name (handling both object and string formats)
+            if isinstance(article['source'], dict):
+                source_name = article['source'].get('name', '')
+            else:
+                source_name = article['source']
+            
+            # Use publishedAt if available, otherwise use published_at
+            published_at = article.get('publishedAt', article.get('published_at', ''))
+            
+            result.append({
+                'rank': i,
+                'title': article['title'],
+                'source': source_name,
+                'url': article['url'],
+                'similarity_score': similarity_score,
+                'published_at': published_at
+            })
+        
+        return result
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..bb54e6cf1b22428ed53e14f5dd328312fe6a904e
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,16 @@
+requests>=2.28.0
+sentence-transformers>=2.2.0
+python-dotenv>=1.0.0
+torch>=2.0.0
+torchvision>=0.15.0
+numpy>=1.20.0
+transformers>=4.20.0
+tqdm>=4.64.0
+google-generativeai>=0.8.0
+flask>=2.0.0
+flask-cors>=3.0.10
+# Optional: if you have an NVIDIA GPU, uncomment the following lines
+# nvidia-cuda-runtime-cu11
+# nvidia-cuda-cupti-cu11
+# nvidia-cudnn-cu11
+# nvidia-cublas-cu11
\ No newline at end of file
diff --git a/static/front.html b/static/front.html
new file mode 100644
index 0000000000000000000000000000000000000000..7c844ef0c6d72a16450198fce126c52f61a37c41
--- /dev/null
+++ b/static/front.html
@@ -0,0 +1,25 @@
+
+
+
+  
+  
+  Fake News Detection Chatbot
+  
+
+
+
+  
+ +
+ + +
+ +
+

Hey,

+

Discover misinformations around you,

+
+ + + + diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000000000000000000000000000000000000..c2bc9a677f272d85fa1da16141aeca564ed206cf --- /dev/null +++ b/static/script.js @@ -0,0 +1,81 @@ +const chat = document.getElementById('chat'); +const input = document.getElementById('query'); +const sendBtn = document.getElementById('sendBtn'); + +function addMessage(text, sender, isPre = false) { + const msg = document.createElement('div'); + msg.classList.add('message', sender); + + // Add special class for pre-formatted messages to style them properly + if (isPre) { + msg.classList.add('pre-formatted'); + + // For pre-formatted text (terminal output) + const pre = document.createElement('pre'); + pre.textContent = text; + + // No inline styles - all styling comes from CSS + msg.appendChild(pre); + } else { + msg.textContent = text; + } + + chat.appendChild(msg); + + // Smooth scroll to new message + setTimeout(() => { + msg.scrollIntoView({ behavior: 'smooth', block: 'end' }); + }, 100); + + return msg; +} + +function extractValuesFromJson(jsonObj) { + if (!jsonObj) return "No data received"; + + // Just output the summary value without any processing + if (jsonObj.summary) { + return jsonObj.summary; + } + + // If no summary field exists, output everything as-is + return JSON.stringify(jsonObj); +} + +async function sendMessage() { + const query = input.value.trim(); + if (!query) return; + + const bg = document.getElementById('chat-background'); + if (bg && !bg.classList.contains('blurred')) { + bg.classList.add('blurred'); + } + + addMessage(query, 'user'); + input.value = ''; + + const loader = addMessage('Processing...', 'bot'); + + try { + const response = await fetch('/api/detect', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query: query }) + }); + + const data = await response.json(); + loader.remove(); + + // Simply extract and display raw values from the JSON response + const rawOutput = extractValuesFromJson(data); + addMessage(rawOutput, 'bot', true); + } catch (e) { + loader.remove(); + addMessage("Error checking news: " + e.message, 'bot'); + } +} + +sendBtn.addEventListener('click', sendMessage); +input.addEventListener('keypress', (e) => { + if (e.key === 'Enter') sendMessage(); +}); diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000000000000000000000000000000000000..b623a875969140545cef4e6cb81161a4c59c15b8 --- /dev/null +++ b/static/style.css @@ -0,0 +1,188 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + height: 100vh; + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* Blurred background image */ +body::before { + content: ""; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: -1; + background: rgb(23, 23, 23); +} + +.chat-container { + flex: 1; + display: flex; + flex-direction: column; + padding: 15px; + overflow-y: auto; + overflow-x: hidden; /* prevent horizontal scrolling */ + margin-bottom: 70px; + background: transparent; + width: 100%; + max-width: 95%; /* wider to accommodate terminal output */ + margin-left: auto; /* center align */ + margin-right: auto; /* center align */ + scroll-behavior: smooth; /* Smooth scrolling */ + height: calc(100vh - 70px); /* Full height minus input area */ +} + +.message { + width: fit-content; /* shrink to text */ + max-width: 100%; /* allow full width for terminal output */ + margin-bottom: 12px; + padding: 12px 15px; + border-radius: 15px; + line-height: 1.4; +} + +/* Special styling for bot messages with pre-formatted text */ +.message.bot.pre-formatted { + width: 100%; /* full width for terminal output */ + max-width: 100%; /* no width restriction */ + white-space: pre-wrap; /* wrap text to prevent horizontal scroll */ + overflow-wrap: break-word; /* break long words if needed */ +} + +.user { + align-self: flex-end; /* right side */ + background: #414141; + color: #fff; + border-bottom-right-radius: 5px; +} + +.bot { + align-self: flex-start; /* left side */ + background: transparent; + color: #ffffff; + border-bottom-left-radius: 5px; +} + +/* Terminal output style - matching exactly what appears in the terminal */ +.message.bot pre { + font-family: monospace; + background-color: transparent; /* No background color */ + color: inherit; /* Use the same text color as the parent */ + padding: 0; + border: none; + width: 100%; + max-height: none; /* No height limit */ + overflow-x: visible; /* No horizontal scrolling */ + white-space: pre-wrap; /* Wrap text to prevent horizontal scrolling */ + font-size: inherit; + line-height: 1.4; +} + + + +.input-area { + position: fixed; + bottom: 1rem; + left: 50%; + transform: translateX(-50%); + width: 100%; + max-width: 95%; /* Match the width of chat container */ + display: flex; + padding: 10px; + background: rgba(54, 54, 54, 0.7); /* make input area semi-transparent */ + box-shadow: 0 -2px 5px rgba(0,0,0,0.05); + border-radius: 30px; + z-index: 10; /* Ensure input stays on top */ +} + +.input-area input { + flex: 1; + padding: 14px 18px; + border: 1px solid #1b1b1b; + box-shadow: #414141; + border-radius: 25px; + outline: none; + font-size: 1rem; +} + +.input-area button { + margin-left: 10px; + padding: 0 20px; + border: none; + background: #1f1f1f; + color: white; + border-radius: 25px; + cursor: pointer; + font-size: 1rem; + transition: background 0.2s ease; +} + +.input-area button:hover { + background: #6a6a6a; +} + +.loader { + font-size: 0.9rem; + color: gray; + margin: 5px 0; +} +.chat-background{ + position: fixed; + font-family: 'Courier New', Courier, monospace; + top: 40%; + left: 50%; + transform: translate(-50%, -50%); + font-weight: bold; + color: rgba(255, 255, 255, 0.8); /* semi-transparent */ + text-align: center; + z-index: 0; /* below chat messages */ + pointer-events: none; /* makes it "untouchable" */ + transition: all 0.4s ease; +} +.chat-background{ + display: inline-block; + text-align: left; +} +.chat-background #p1 { + font-size: 4rem; +} + +.chat-background #p2 { + font-size: 3rem; +} + +/* When blurred */ +.chat-background.blurred { + filter: blur(12px); /* strong blur */ + opacity: 0.4; /* fade slightly for readability */ + transition: all 0.4s ease; +} + +@media (max-width: 600px) { + .message { + max-width: 85%; + } + + .chat-container { + padding: 10px; + max-width: 100%; + } + + .input-area { + max-width: 95%; + bottom: 0.5rem; + } + + .chat-background #p1 { + font-size: 3rem; + } + + .chat-background #p2 { + font-size: 2rem; + } +} diff --git a/whitelisted_domains.py b/whitelisted_domains.py new file mode 100644 index 0000000000000000000000000000000000000000..5a5499660b5023f686e0fec94225fed4c659c0b7 --- /dev/null +++ b/whitelisted_domains.py @@ -0,0 +1,59 @@ +""" +whitelisted_domains.py - Module containing the list of trusted news domains + +This list is used to filter articles from trusted sources. +""" + +WHITELISTED_DOMAINS = [ + # United States + "nytimes.com", "washingtonpost.com", "wsj.com", "cnn.com", "reuters.com", "apnews.com", + "usatoday.com", "npr.org", "latimes.com", "foxnews.com", "nbcnews.com", "cbsnews.com", + "politico.com", "thehill.com", "bloomberg.com", "cnbc.com", "abcnews.go.com", + "time.com", "newyorker.com", "theatlantic.com", "slate.com", "vox.com", "buzzfeednews.com", + "axios.com", "foreignpolicy.com", "pbs.org", "msnbc.com", "thedailybeast.com", + "propublica.org", "motherjones.com", "fivethirtyeight.com", "bostonglobe.com", + "chicagotribune.com", "sfchronicle.com", "seattletimes.com", "dallasnews.com", + "newsweek.com", "usnews.com", "businessinsider.com", "forbes.com", "fortune.com", + + # United Kingdom + "bbc.co.uk", "bbc.com", "theguardian.com", "telegraph.co.uk", "independent.co.uk", "thetimes.co.uk", + "ft.com", "economist.com", "reuters.co.uk", "sky.com", "channel4.com", + "dailymail.co.uk", "mirror.co.uk", "express.co.uk", "standard.co.uk", "spectator.co.uk", + "newstatesman.com", "politico.eu", "inews.co.uk", "metro.co.uk", "theweek.co.uk", + "lbc.co.uk", "itv.com", "newscientist.com", "wired.co.uk", "theconversation.com", + + # India + "thehindu.com", "timesofindia.indiatimes.com", "indianexpress.com", "ndtv.com", + "hindustantimes.com", "news18.com", "economictimes.indiatimes.com", "thequint.com", + "telegraphindia.com", "theprint.in", "thelogicalindian.com", "thewire.in", "indiaspend.com", + "businesstoday.in", "livemint.com", "firstpost.com", "indiatoday.in", "outlookindia.com", + "financialexpress.com", "deccanherald.com", "tribuneindia.com", "thehindubusinessline.com", + "thestatesman.com", "newindianexpress.com", "asianage.com", "dnaindia.com", + "moneycontrol.com", "business-standard.com", "frontline.thehindu.com", "aajtak.intoday.in", + "zeenews.india.com", "republicworld.com", "abplive.com", "thebridge.in", "barandbench.com", + "livelaw.in", "newslaundry.com", "caravan.com", "indialegallive.com", "downtoearth.org.in", + "forbesindia.com", "swarajyamag.com", "thenewsminute.com", "exchange4media.com", "medianama.com", + "yourstory.com", "inc42.com", "entrackr.com", "vccircle.com", "thekashmirmonitor.net", + "kashmirtimes.com", "greaterkashmir.com", "northeasttoday.in", "eastmojo.com", "nenow.in", + "sentinelassam.com", "nagalandpost.com", "morungexpress.com", "theshillongtimes.com", + "thesangaiexpress.com", "echoofindia.com", "sikhtimes.com", "punjabnewsexpress.com", + "daijiworld.com", "thehansindia.com", "telanganatoday.com", "deccanchronicle.com", + "mathrubhumi.com", "manoramaonline.com", "theweek.in", "opindia.com", "jansatta.com", + + # Canada + "cbc.ca", "globalnews.ca", "thestar.com", "nationalpost.com", "theglobeandmail.com", + "ctv.ca", "ctvnews.ca", "torontosun.com", "macleans.ca", "huffingtonpost.ca", + + # Australia + "abc.net.au", "smh.com.au", "theage.com.au", "news.com.au", "9news.com.au", + "theaustralian.com.au", "sbs.com.au", "theguardian.com.au", "canberratimes.com.au", + + # International + "aljazeera.com", "france24.com", "dw.com", "euronews.com", "un.org", "who.int", + "hrw.org", "amnesty.org", "project-syndicate.org", "nature.com", "sciencemag.org", + "scientificamerican.com", "spiegel.de/international", "lemonde.fr/en", "japantimes.co.jp", + "scmp.com", "straitstimes.com", "thejakartapost.com", "koreatimes.co.kr", + "themoscowtimes.com", "kyivpost.com", "haaretz.com", "jpost.com", "arabnews.com", + "timesofisrael.com", "middleeasteye.net", "gulf-times.com", "thenational.ae", + "egyptindependent.com", "mg.co.za", "news24.com", "afr.com" +]