Mercurial
changeset 60:d64a8c189a77
Merged
| author | June Park <me@mrjunejune.com> |
|---|---|
| date | Sat, 20 Dec 2025 13:56:01 -0500 |
| parents | 983769fba767 (diff) e06bc03d9618 (current diff) |
| children | 9df5587cf23b |
| files | |
| diffstat | 121 files changed, 3243 insertions(+), 230 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/README.md Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,3 @@ +# Asyncio + +Wanted to know how asycio works in python so made few questiosn that I would probably ask others to show how to do use these.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/bank_question/README.md Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,34 @@ +# Bank Account + +You are given a simplified multi-threaded “bank account” implementation used by one of our infrastructure teams. The current code processes deposits concurrently but produces incorrect results. + +``` +class BankAccount: + def __init__(self, balance): + self.balance = balance + + def deposit(self, amount): + new_balance = self.balance + amount # read + time.sleep(0.1) # simulate delay + self.balance = new_balance # write + +account = BankAccount(0) + +with ThreadPoolExecutor(max_workers=2) as executor: + futures = [ + executor.submit(account.deposit, 500), + executor.submit(account.deposit, 700), + ] +``` + +Task + +Identify the concurrency bugs in the implementation. + +Explain why these bugs occur. + +Fix the implementation without modifying the existing BankAccount class directly. + +You may use subclassing (Python) or embedding (Go-style composition), depending on the language you choose. + +Ensure your solution is thread-safe and produces the correct final balance (1200).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/bank_question/main.py Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,43 @@ +# # Bank Account +# +# You are given a simplified multi-threaded “bank account” implementation used by one of our infrastructure teams. The current code processes deposits concurrently but produces incorrect results. +# +# Task +# +# Identify the concurrency bugs in the implementation. +# +# Explain why these bugs occur. +# +# Fix the implementation without modifying the existing BankAccount class directly. +# +# You may use subclassing (Python) or embedding (Go-style composition), depending on the language you choose. +# +# Ensure your solution is thread-safe and produces the correct final balance (1200). + +from threading import Lock +from concurrent.futures import ThreadPoolExecutor +import time + +class BankAccount: + def __init__(self, balance): + self.balance = balance + self._lock = Lock() + + def deposit(self, amount): + while (self._lock.locked()): + pass + self._lock.acquire() + new_balance = self.balance + amount # read + time.sleep(0.1) # simulate delay + self.balance = new_balance # write + self._lock.release() + + +account = BankAccount(0) + +with ThreadPoolExecutor(max_workers=2) as executor: + futures = [ + executor.submit(account.deposit, 500), + executor.submit(account.deposit, 700), + ] +print(account.balance)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/bucket_questions/README.md Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,14 @@ +# Bucket questions + +## Context + +Please implement two functions: refillTokenBucket and useTokens. Two classes are already defined: DistributedCache and TokenBucket. + +refillTokenBucket(user_id): Refill tokens for the specified user's bucket. + +useTokens(user_id, tokens): Check if there are enough tokens in the specified user's token bucket. If so, update the remaining token count and return true; otherwise, return false. You are required to use instances of the existing classes to implement these functions. + +## Requirements + +Use the API provided by DistributedCache to get and update the user's TokenBucket. +Implement the functionality using existing class instances and ensure the behavior is correct across multiple calls
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/bucket_questions/main.py Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,65 @@ +import asyncio +from concurrent.futures import ThreadPoolExecutor +from typing import Dict + +class DistributedCache: + + def __init__(self): + self._user_bucket_map: Dict[str, TokenBucket] = {} + + + def get_bucket(self, user_id: str): + return self._user_bucket_map[user_id] + + def set_bucket(self, user_id: str): + self._user_bucket_map[user_id] = TokenBucket() + + +INITIAL_VALUES = 10 + +class TokenBucket: + + def __init__(self, initial_values = INITIAL_VALUES): + self._tokens = initial_values + self._refill_values = initial_values + self._lock = asyncio.Lock() + + def get_tokens(self): + print(self._tokens) + return self._tokens + + async def consume_tokens(self, token: int): + async with self._lock: + if self.get_tokens() < token: + return False + + await asyncio.sleep(1) + self._tokens -= token + return True + + async def refill_tokens(self): + async with self._lock: + self._tokens = self._refill_values + + +cache = DistributedCache() + +user_1 = "JUNE" +user_2 = "VICTOR" +cache.set_bucket(user_1) +cache.set_bucket(user_2) + +async def refill_token_bucket(user_id: str): + await cache.get_bucket(user_id).refill_tokens() + +async def use_tokens(user_id: str, tokens: int): + return await cache.get_bucket(user_id).consume_tokens(tokens) + +def run(): + with ThreadPoolExecutor(max_workers=3) as thread: + thread.submit(asyncio.run, use_tokens(user_1, 10)) + thread.submit(asyncio.run, use_tokens(user_1, 10)) + thread.submit(asyncio.run, use_tokens(user_2, 10)) + +if __name__ == "__main__": + run()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/bucket_questions/single_thread_async.py Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,67 @@ +import asyncio +from typing import Dict + +class DistributedCache: + + def __init__(self): + self._user_bucket_map: Dict[str, TokenBucket] = {} + + + def get_bucket(self, user_id: str): + return self._user_bucket_map[user_id] + + def set_bucket(self, user_id: str): + self._user_bucket_map[user_id] = TokenBucket() + + +INITIAL_VALUES = 10 + +class TokenBucket: + + def __init__(self, initial_values = INITIAL_VALUES): + self._tokens = initial_values + self._refill_values = initial_values + self._lock = asyncio.Lock() + + def get_tokens(self): + print(self._tokens) + return self._tokens + + async def consume_tokens(self, token: int): + async with self._lock: + if self.get_tokens() < token: + return False + + await asyncio.sleep(1) + self._tokens -= token + return True + + async def refill_tokens(self): + async with self._lock: + self._tokens = self._refill_values + + +cache = DistributedCache() + +user_1 = "JUNE" +user_2 = "VICTOR" +cache.set_bucket(user_1) +cache.set_bucket(user_2) + +async def refill_token_bucket(user_id: str): + await cache.get_bucket(user_id).refill_tokens() + +async def use_tokens(user_id: str, tokens: int): + return await cache.get_bucket(user_id).consume_tokens(tokens) + +async def run(): + return_value = await asyncio.gather( + use_tokens(user_1, 10), + refill_token_bucket(user_1), + use_tokens(user_1, 10), + use_tokens(user_2, 10), + ) + print(return_value) + +if __name__ == "__main__": + asyncio.run(run())
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/database/README.md Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,19 @@ +# Design an in-memory key-value database that supports the following commands and fully handles nested transactions. + +## Core Operations: +SET <key> <value>: Sets the value for a key. + +GET <key>: Returns the value for a key. + +UNSET <key>: Removes the key and its value. + +## Transaction Operations: +BEGIN: Starts a new transaction scope. If a transaction is already active, this starts a nested transaction. + +COMMIT: Applies all changes made in the current transaction scope and all its active nested transactions to the parent scope (or to the main database state if no parent exists). After a successful commit, the transaction scope is closed. + + ROLLBACK: Discards all changes made in the current transaction scope and all its active nested transactions, restoring the state to what it was when the BEGIN command was issued for the current scope. After a successful rollback, the transaction scope is closed. + +## Implementation Goal: + +Design the primary data structures and outline the logic for SET, COMMIT, and ROLLBACK to ensure nested transactions operate correctly. Explain how the state of the database is managed across multiple transaction layers.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/database/main.py Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,16 @@ +class Database: + + def __init__(self): + self.db = {} + self.state = 'idle' + + def set(self, key, value): + self.db[key] = value + + def get(self, key): + return self.db[key] + + def unset(self, key): + del self.db[key] + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/frontend/README.md Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,13 @@ +Frontend Task + +1. Component Implementation: Enhanced TypeAhead (Good?) + +Develop a TypeAhead input component that filters a dataset based on user entry. Implement a debounce mechanism to optimize performance by limiting the frequency of function execution during typing. Apply text processing logic to identify and visually highlight the specific substrings within the results that match the user's input. + +2. Algorithmic Challenge: Increasing Subsequence(Okay?) + +Analyze a given array of integers and write a function to determine the length of the longest subsequent growing sequence contained within the array. + +3. Frontend Architecture: AI Prompt and Context Interface + +Construct a dual-pane user interface featuring a prompt history sidebar on the left and a main content area on the right for input and context display. Implement data fetching from the OpenAI API. Develop state management logic where submitting a new prompt triggers an API call, and selecting a specific prompt from the history injects the corresponding past response to serve as the context for the subsequent request.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/frontend/index.html Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,7 @@ +<HTML> + <body> + <input id="search" type="text"></input> + <div id="suggestion"></div> + </body> + <script src="./main.js"></script> +</HTML>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/frontend/longest_subinteger.py Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,53 @@ +# Analyze a given array of integers and write a function to determine the length of the longest subsequent growing sequence contained within the array. + + +# 1,2,3,4,2,1,3 +# | + +# Example 1: +# +# Input: nums = [10,9,2,5,3,7,101,18] +# [(2, 1), 3, 5, 7, 9, 10, 18, 101] +# | +# Output: 4 + +# Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4. + +# Example 2: +# +# Input: nums = [0,1,0,3,2,3] +# Output: 4 +# Example 3: +# +# Input: nums = [7,7,7,7,7,7,7] +# Output: 1 + + +# Longest Increasing Subsequence +def main(nums): + ans = 0 + cache = set() + + def dfs(val, pos, curr_ans): + nonlocal ans + + if ((val, pos, curr_ans) in cache): + return + + if pos > len(nums): + return + if nums[pos] > val: + curr_ans += 1 + else: + ans = max(ans, curr_ans) + return + for i in range(pos, len(nums)): + dfs(nums[pos], i, curr_ans) + + for i in range(len(nums)): + dfs(float("-inf"), i, 0) + + return ans + + +print(main([10,9,2,5,3,7,101,18]))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/frontend/main.js Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,135 @@ +// Develop a TypeAhead input component that filters a dataset based on user entry. Implement a debounce mechanism to optimize performance by limiting the frequency of function execution during typing. Apply text processing logic to identify and visually highlight the specific substrings within the results that match the user's input. +// + + +// 3. text processing logic which ones... + + +let previousId = 0; +const inputEle = document.getElementById("search"); +const divSuggestion = document.getElementById("suggestion"); +const tries = {} +const userNames = [ + "Ephemeral", + "Mellifluous", + "Ubiquitous", + "Voracious", + "Serendipity", + "Quixotic", + "Languid", + "Ephemeral", + "Susurrus", + "Ebullient", + "Petrichor", + "Ineluctable", + "Platitude", + "Ennui", + "Axiom", + "Cacophony", + "Halcyon", + "Ineffable", + "Juxtapose", + "Lugubrious", + "Nefarious", + "Ostracize", + "Pernicious", + "Ruminate", + "Solipsism", + "Taciturn", + "Wanderlust", + "Zephyr", + "Discombobulate", + "Esoteric", + "Incendiary", + "Kudos", + "Misanthrope", + "Pensive", + "Quagmire", + "Ramify", + "Stymie", + "Unctuous", + "Vex", + "Wistful", + "Banal", + "Dichotomy", + "Fecund", + "Garrulous", + "Hegemony", + "Imbroglio", + "Knell", + "Mirth", + "Obfuscate", + "Parsimonious", +] + +function createTries(word) { + curr = tries + for (let letter of word.toLowerCase()) { + if (!curr[letter]) { + curr[letter] = {}; + } + if (!curr[letter]["#"]) { + curr[letter]["#"] = [] + curr[letter]["#"].push(word) + } else { + curr[letter]["#"].push(word) + } + + curr = curr[letter]; + } +} + +userNames.forEach((word) => createTries(word)); + +function findWord(word) { + const results = new Set() + + function dfs(currTries, currWordPosition, fuzz_search, curr_sequential, max_sequential = 0) { + if (currWordPosition === word.length || fuzz_search > 1) { + console.log(currTries, currWordPosition, fuzz_search, curr_sequential, max_sequential) + if (max_sequential >= 2) { + currTries['#'].forEach((word) => results.add(word)); + console.log("hello") + } + return; + } + letter = word[currWordPosition].toLowerCase(); + Object.keys(currTries).forEach((key) => { + if (key === '#') return; + dfs(currTries[key], + currWordPosition + 1, + (key != letter) ? fuzz_search + 1 : fuzz_search, + (key == letter) ? curr_sequential + 1 : 0, + Math.max((key == letter) ? curr_sequential + 1 : 0, max_sequential) + ); + }) + } + + dfs(tries, 0, 0, 0) + return results; +} +function populateSuggestions(inputValue) { + const words = findWord(inputValue); + + divSuggestion.innerHTML = ""; + for (let word of words) { + let res = ""; + const lowerInput = inputValue.toLowerCase(); + for (let letter of word) { + if (lowerInput.includes(letter.toLowerCase())) { + res += `<span style="color: red;">${letter}</span>`; + } else { + res += letter; + } + } + divSuggestion.innerHTML += res + "<br>"; + } +} + +let i = 0 +inputEle.addEventListener('keypress', () => { + if (previousId) { + clearTimeout(previousId); + } + previousId = setTimeout(() => populateSuggestions(inputEle.value), 500); // 1, 2, 3 +})
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/inference/README.md Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,30 @@ +Inference Questions + +Context + +You are tasked with building a simplified inference engine component responsible for handling incoming user requests for a large language model (LLM). To optimize throughput and GPU utilization, the engine must batch multiple requests together, run the inference call once per batch, and then deconstruct the results to return token-level output to the individual users. + +Objective + +Complete the provided Python class, BatchInferenceEngine by implementing the methods necessary to: +Queue incoming user requests. +Process a batch when the queue reaches a defined batch size. + +Simulate the token-level output from an LLM and correctly associate each generated token with its original request. + + +Task Requirements + + + + + +Implement the logic for $enqueue\_request$. + + + +Implement the logic for $\_process\_batch$. + + + +Demonstrate the usage by creating 7 unique requests and enqueueing them one by one. Show the state of the queue and the processed tokens after each batch run.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/inference/main.py Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,107 @@ +from typing import List, Dict, Any, Optional + +class UserRequest: + """Represents an incoming user request.""" + def __init__(self, request_id: int, prompt: str): + self.request_id = request_id + self.prompt = prompt + self.output_tokens: List[str] = [] # Stores tokens as they are generated + + def __repr__(self): + return f"Request(ID={self.request_id}, Prompt='{self.prompt[:20]}...', Tokens={len(self.output_tokens)})" + +# --- Mock LLM Interface --- +def mock_inference_call(prompts: List[str], batch_id: int) -> Dict[str, Any]: + """ + Simulates the call to the underlying LLM/GPU. + + In a real scenario, this returns generated tokens and associated metadata. + For this mock, we return a flat list of tokens, one for each request + in the batch, and the batch ID for verification. + + The length of the tokens list MUST equal the length of the prompts list. + """ + print(f" [INFERENCE] Running Batch {batch_id} with {len(prompts)} requests...") + results = { + 'batch_id': batch_id, + # Simulate generating a single new token for each request in the batch + 'generated_tokens': [ + f"token_{i+1}_of_{batch_id}" for i, _ in enumerate(prompts) + ] + } + return results +# ------------------------- + + +class BatchInferenceEngine: + """ + A simplified inference engine that handles request batching and + token-level result distribution. + """ + def __init__(self, batch_size: int = 4): + self.batch_size = batch_size + self.request_queue: List[UserRequest] = [] + self.next_batch_id = 1 + + def enqueue_request(self, request: UserRequest) -> None: + """ + Adds a new request to the queue and triggers batch processing if + the batch size is reached. + """ + # --- YOUR CODE HERE --- + # 1. Add the request to the queue. + # 2. Check if the queue size meets or exceeds self.batch_size. + # 3. If so, call self._process_batch(). + # ---------------------- + self.request_queue.append(request) + while len(self.request_queue) > self.batch_size: + self._process_batch() + + + def _process_batch(self) -> None: + """ + Executes the inference call for the current batch and distributes + the results back to the individual requests. + """ + batch = self.request_queue[:self.batch_size] + prompts = map(lambda x : x.prompt, batch) + results = mock_inference_call(list(prompts), self.next_batch_id) + for request in self.request_queue: + request.output_tokens = results["generated_tokens"] + self.request_queue[self.batch_size:] + self.next_batch_id += 1 + + def get_results(self, request_id: int) -> Optional[List[str]]: + """ + In a real system, this would retrieve results from a separate + completed-requests store. For this mock, assume we can only + retrieve results for requests that have been fully processed and + are no longer in the queue. + """ + # For simplicity, assume all requests that have been processed + # by a batch call have completed their generation for this *single step* + # of the mock. If you want to make this more realistic, feel free to + # expand the UserRequest class to include a 'is_complete' flag. + + # For the provided mock structure, we'll just check the queue: + + for req in self.request_queue: + if req.request_id == request_id: + # If it's still in the queue, it hasn't been processed yet + return None + + # In a complete system, you'd look up the request ID in a completed-requests map. + # For this simplified version, let's just return a simulated result for + # requests that *would* have been processed: + + # If the request ID is less than the ID of the requests that would be + # processed in the *next* batch, we simulate a complete token output. + if request_id < self.next_batch_id * self.batch_size: + # Simple simulation: + return [f"token_X_of_{batch_num}" for batch_num in range(1, self.next_batch_id)] + + return None + + +def main(): + pass
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncio_threads/stop_token/main.py Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,42 @@ +from typing import Generator +from typing import List + +# [Hello There, "I am <EN", "D> Something"] +# | + +def truncate_stream(text_stream: Generator[str, None, None], stop_token: str) -> Generator[str, None, None]: + buffer = "" + for chunk in text_stream: + can_be_prefix = False + buffer += chunk + if stop_token not in buffer: + for end in range(1, len(stop_token)): + if stop_token[:end] in buffer[-1 * len(stop_token):]: + can_be_prefix = True + if not can_be_prefix: + yield chunk + buffer = "" + else: + pos = buffer.find(stop_token) + yield buffer[:pos] + return + +def stream_to_list(stream: Generator[str, None, None]) -> List[str]: + return [chunk for chunk in stream] + +def list_to_stream(chunks: List[str]) -> Generator[str, None, None]: + for chunk in chunks: + yield chunk + + +print( + stream_to_list( + truncate_stream(list_to_stream(["Hello there ", "I'm doing great today.<END>", "Thanks for asking.", "How are you"]), "<END>") + ) +) + +print( + stream_to_list( + truncate_stream(list_to_stream(["Hello there ", "I'm doing great today.<E", "N", "D>", "Thanks for asking.", "How are you"]), "<END>") + ) +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/.bazelrc Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,5 @@ +common --enable_workspace +common --experimental_google_legacy_api +common --experimental_enable_android_migration_apis +# Necesary until bazel 7.2.0rc2 or later is released (https://github.com/bazelbuild/bazel/issues/22415) +common --nocheck_visibility
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/.bazelversion Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,1 @@ +7.2.0rc1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/BUILD Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,7 @@ +platform( + name = "arm64-v8a", + constraint_values = [ + "@platforms//cpu:arm64", + "@platforms//os:android", + ], +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/README.md Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,96 @@ + +# Bazel Firebase Cloud Messaging (FCM) example + +FCM requires certain information about your app (API key, app ID, project id, +etc) to be present in the `res/values/values.xml` resource file. This example +shows how to use the tools provided in the +[bazelbuild/tools_android](https://github.com/bazelbuild/tools_android) repo to +generate the `values.xml` file from the `google-services.json` file from your +Firebase console. + +## Building the Example + +To build the example: + +1. Make sure the `ANDROID_HOME` environment variable is set to the absolute path + of your Android SDK. + +2. Go to the Firebase console for your project, and in Settings, download + `google-service.json`, and replace the sample file in the `app` directory. + +3. Run `bazel build //app` in the project. + +## Applying the Example to Your Code + +To apply this example to your code: + +1. Add the following to your `WORKSPACE` file: +```python +TOOLS_ANDROID_VERSION = "0.1" +http_archive( + name = "tools_android", + strip_prefix = "tools_android-" + TOOLS_ANDROID_VERSION, + url = "https://github.com/bazelbuild/tools_android/archive/%s.tar.gz" % TOOLS_ANDROID_VERSION, +) +load("@tools_android//tools/googleservices:defs.bzl", "google_services_workspace_dependencies") +google_services_workspace_dependencies() +``` + +2. Add the following to your `BUILD` file: +```python +load("@tools_android//tools/googleservices:defs.bzl", "google_services_xml") + +GOOGLE_SERVICES_XML = google_services_xml( + package_name = "com.example.myapplication", + google_services_json = "google-services.json" +) +``` + +3. Add `GOOGLE_SERVICES_XML` to the `resource_files` attribute of your + `android_binary` rule. For example: +```python +android_binary( + ... + resource_files = glob(["src/main/res/**"]) + GOOGLE_SERVICES_XML, + ... +) +``` + +4. Bazel's `AndroidManifest.xml` merging logic does not merge permissions from + dependent libraries (see issue [#5411](https://github.com/bazelbuild/bazel/issues/5411)). + You may need to add the following permissions to the `AndroidManifest.xml` of + your top-level `android_binary` rule: +```xml +<uses-permission android:name="android.permission.INTERNET" /> +<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> +<uses-permission android:name="android.permission.WAKE_LOCK" /> +<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> +``` + +## Manual Integration + +It's also possible to run the Google Services values.xml generator manually and +add the results to your project: + +1. Go to the Firebase console for your project, and in Settings, download + `google-service.json`. + +2. From the workspace root of the `tools_android` project, run the Google + Services XML generator: +``` + bazel run //third_party/googleservices:GenerateGoogleServicesXml -- \ + com.example.myapplication \ + /absolute/path/to/google-services.json \ + /tmp/values.xml +``` + The arguments are the package name for your app, the absolute file path to + the `google-services.json` file, and finally the file path for `values.xml`. + +3. Merge the resulting `values.xml` file into your `values.xml` file (or put the + file into your `res/values` directory if you don't already have a + `values.xml` file). Alternatively, the `values.xml` file can be put into a + separate `res/values` directory and added to the `resource_files`. For the + example here, if `values.xml` is in + `app/src/main/google_services_xml/res/values/values.xml`, the `BUILD` file + would have + `resource_files = glob(["src/main/res/**"]) + ["src/main/google_services_xml/res/values/values.xml"],`.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/WORKSPACE Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,81 @@ +# FIXME(alexeagle): move to bzlmod +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +RULES_JVM_EXTERNAL_TAG = "5.3" + +RULES_JVM_EXTERNAL_SHA = "d31e369b854322ca5098ea12c69d7175ded971435e55c18dd9dd5f29cc5249ac" + +http_archive( + name = "rules_jvm_external", + sha256 = RULES_JVM_EXTERNAL_SHA, + strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, + url = + "https://github.com/bazelbuild/rules_jvm_external/releases/download/%s/rules_jvm_external-%s.tar.gz" % (RULES_JVM_EXTERNAL_TAG, RULES_JVM_EXTERNAL_TAG), +) + +load("@rules_jvm_external//:defs.bzl", "maven_install") + +maven_install( + aar_import_bzl_label = "@rules_android//rules:rules.bzl", + artifacts = [ + "com.google.firebase:firebase-messaging:17.0.0", + "com.android.support:appcompat-v7:26.1.0", + "com.android.support.constraint:constraint-layout:1.0.2", + "com.google.code.gson:gson:2.8.2", + ], + # See https://github.com/bazelbuild/rules_jvm_external/#repository-aliases + # This can be removed if none of your external dependencies uses `maven_jar`. + generate_compat_repositories = True, + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], + use_starlark_android_rules = True, + version_conflict_policy = "pinned", +) + +load("@maven//:compat.bzl", "compat_repositories") + +compat_repositories() + +TOOLS_ANDROID_COMMIT = "0e864ba5a86958513658250de587416d8e17c481" + +http_archive( + name = "tools_android", + repo_mapping = { + "@com_google_code_gson_2_8_2": "@com_google_code_gson_gson", + }, + sha256 = "57c50d6331ba578fc8adfcede20ef12b437cb4cc2edf60c852e4b834eefee5fc", + strip_prefix = "tools_android-" + TOOLS_ANDROID_COMMIT, + url = "https://github.com/bazelbuild/tools_android/archive/%s.tar.gz" % TOOLS_ANDROID_COMMIT, +) + +RULES_ANDROID_COMMIT = "93e27030d3f0defa39cbbc35195638cb772b0c27" + +http_archive( + name = "rules_android", + sha256 = "71cae2413868a24f17d43fd595af6f3905d2e5b3235f76514f54800bfd90c903", + strip_prefix = "rules_android-" + RULES_ANDROID_COMMIT, + urls = ["https://github.com/bazelbuild/rules_android/archive/%s.zip" % RULES_ANDROID_COMMIT], +) + +load("@rules_android//:prereqs.bzl", "rules_android_prereqs") + +rules_android_prereqs() + +load("@rules_android//:defs.bzl", "rules_android_workspace") + +rules_android_workspace() + +load("@rules_android//rules:rules.bzl", "android_sdk_repository") + +# Requires that the ANDROID_HOME environment variable is set to the Android SDK path. +android_sdk_repository( + name = "androidsdk", +) + +register_toolchains( + "@rules_android//toolchains/android:android_default_toolchain", + "@rules_android//toolchains/android_sdk:android_sdk_tools", +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/app/BUILD Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,29 @@ +load("@rules_android//android:rules.bzl", "android_binary") +load("@tools_android//tools/googleservices:defs.bzl", "google_services_xml") + +GOOGLE_SERVICES_XML = google_services_xml( + package_name = "com.example.myapplication", + google_services_json = "google-services.json", +) + +android_binary( + name = "app", + srcs = glob(["src/main/java/**/*.java"]), + # this sets the java package for the R class, since this android_binary + # rule isn't under a java root (i.e., some directory named "java" for the + # root of the java code for the app). + custom_package = "com.example.myapplication", + manifest = "src/main/AndroidManifest.xml", + manifest_values = { + "minSdkVersion": "15", + "applicationId": "com.example.myapplication", + }, + resource_files = glob(["src/main/res/**"]) + GOOGLE_SERVICES_XML, + deps = [ + "@maven//:com_google_firebase_firebase_messaging", + "@maven//:com_google_firebase_firebase_iid", + "@maven//:com_android_support_appcompat_v7", + # activity_main layout uses contraints + "@maven//:com_android_support_constraint_constraint_layout", + ], +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/app/google-services.json Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,42 @@ +{ + "project_info": { + "project_number": "12345678901", + "firebase_url": "https://replace.with.your.project.example.com", + "project_id": "example-123", + "storage_bucket": "replace.with.your.project.example.org" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:12345678901:android:abcdef1111111111", + "android_client_info": { + "package_name": "com.example.myapplication" + } + }, + "oauth_client": [ + { + "client_id": "12345678901-abcdabcdabcdabcdabcdabcdabcdabcd.apps.googleusercontent.com", + "client_type": 1 + } + ], + "api_key": [ + { + "current_key": "abcdef12345678901_abcdef123456_abcdef12" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 1, + "other_platform_oauth_client": [] + }, + "ads_service": { + "status": 2 + } + } + } + ], + "configuration_version": "1" +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/app/src/main/AndroidManifest.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.myapplication"> + + <!-- Bazel doesn't merge perimissions from libraries / aars, so these are + necessary in the AndroidManifest.xml for the top-level android_binary --> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> + + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/AppTheme"> + + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <service + android:name=".MyFirebaseInstanceIdService"> + <intent-filter> + <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/> + </intent-filter> + </service> + + <service + android:name=".MyFirebaseMessagingService"> + <intent-filter> + <action android:name="com.google.firebase.MESSAGING_EVENT"/> + </intent-filter> + </service> + + </application> + +</manifest>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/app/src/main/java/com/example/myapplication/MainActivity.java Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,18 @@ +package com.example.myapplication; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; + +import com.google.firebase.iid.FirebaseInstanceId; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Log.i("myapp", "token: " + FirebaseInstanceId.getInstance().getToken()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/app/src/main/java/com/example/myapplication/MyFirebaseInstanceIdService.java Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,21 @@ +package com.example.myapplication; + +import android.util.Log; + +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.iid.FirebaseInstanceIdService; + +public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService { + @Override + public void onTokenRefresh() { + // Get updated InstanceID token. + String refreshedToken = FirebaseInstanceId.getInstance().getToken(); + Log.d("myapp", "Refreshed token: " + refreshedToken); + + // If you want to send messages to this application instance or + // manage this apps subscriptions on the server side, send the + // Instance ID token to your app server. + } + +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/app/src/main/java/com/example/myapplication/MyFirebaseMessagingService.java Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,39 @@ +package com.example.myapplication; + +import android.util.Log; + +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; + +public class MyFirebaseMessagingService extends FirebaseMessagingService { + @Override + public void onMessageReceived(RemoteMessage remoteMessage) { + + // TODO(developer): Handle FCM messages here. + // Not getting messages here? See why this may be: https://goo.gl/39bRNJ + Log.d("myapp", "From: " + remoteMessage.getFrom()); + + // Check if message contains a data payload. + if (remoteMessage.getData().size() > 0) { + Log.d("myapp", "Message data payload: " + remoteMessage.getData()); + + if (/* Check if data needs to be processed by long running job */ true) { + // For long-running tasks (10 seconds or more) use Firebase Job Dispatcher. + //scheduleJob(); + } else { + // Handle message within 10 seconds + //handleNow(); + } + + } + + // Check if message contains a notification payload. + if (remoteMessage.getNotification() != null) { + Log.d("myapp", "Message Notification Body: " + remoteMessage.getNotification().getBody()); + } + + // Also if you intend on generating your own notifications as a result of a received FCM + // message, here is where that should be initiated. See sendNotification method below. + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/app/src/main/res/drawable-v24/ic_launcher_foreground.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,34 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillType="evenOdd" + android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" + android:strokeColor="#00000000" + android:strokeWidth="1"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="78.5885" + android:endY="90.9159" + android:startX="48.7653" + android:startY="61.0927" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0" /> + <item + android:color="#00000000" + android:offset="1.0" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" + android:strokeColor="#00000000" + android:strokeWidth="1" /> +</vector>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/app/src/main/res/drawable/ic_launcher_background.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillColor="#26A69A" + android:pathData="M0,0h108v108h-108z" /> + <path + android:fillColor="#00000000" + android:pathData="M9,0L9,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,0L19,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,0L29,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,0L39,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,0L49,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,0L59,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,0L69,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,0L79,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M89,0L89,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M99,0L99,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,9L108,9" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,19L108,19" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,29L108,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,39L108,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,49L108,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,59L108,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,69L108,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,79L108,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,89L108,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,99L108,99" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,29L89,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,39L89,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,49L89,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,59L89,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,69L89,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,79L89,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,19L29,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,19L39,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,19L49,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,19L59,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,19L69,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,19L79,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> +</vector>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/app/src/main/res/layout/activity_main.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="com.example.myapplication.MainActivity"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Hello World!" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + +</android.support.constraint.ConstraintLayout>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file
Binary file gara/android/firebase-cloud-messaging/app/src/main/res/mipmap-mdpi/ic_launcher.png has changed
Binary file gara/android/firebase-cloud-messaging/app/src/main/res/mipmap-mdpi/ic_launcher_round.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/app/src/main/res/values/colors.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="colorPrimary">#3F51B5</color> + <color name="colorPrimaryDark">#303F9F</color> + <color name="colorAccent">#FF4081</color> +</resources>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/app/src/main/res/values/strings.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,3 @@ +<resources> + <string name="app_name">My Application</string> +</resources>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/firebase-cloud-messaging/app/src/main/res/values/styles.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,11 @@ +<resources> + + <!-- Base application theme. --> + <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> + <!-- Customize your theme here. --> + <item name="colorPrimary">@color/colorPrimary</item> + <item name="colorPrimaryDark">@color/colorPrimaryDark</item> + <item name="colorAccent">@color/colorAccent</item> + </style> + +</resources>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/.bazelversion Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,1 @@ +6.5.0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/BUILD.bazel Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,17 @@ +load("@io_bazel_rules_kotlin//kotlin:core.bzl", "kt_compiler_plugin") + +kt_compiler_plugin( + name = "jetpack_compose_compiler_plugin", + id = "androidx.compose.compiler", + target_embedded_compiler = True, + visibility = ["//visibility:public"], + deps = ["@maven//:androidx_compose_compiler_compiler"], +) + +platform( + name = "arm64-v8a", + constraint_values = [ + "@platforms//cpu:arm64", + "@platforms//os:android", + ], +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/MODULE.bazel Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,38 @@ +"Bazel dependencies" + +bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_jvm_external", version = "5.3") + +maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") +maven.install( + artifacts = [ + "androidx.appcompat:appcompat:1.5.1", + # Jetpack Compose Dependencies + "androidx.activity:activity-compose:1.6.0", + "androidx.compose.material:material:1.2.1", + "androidx.compose.ui:ui:1.2.1", + "androidx.compose.ui:ui-tooling:1.2.1", + "androidx.compose.compiler:compiler:1.3.2", + "androidx.compose.runtime:runtime:1.2.1", + # Dependencies needed to manage version conflicts + "androidx.core:core:1.6.0", + "androidx.core:core-ktx:1.6.0", + "androidx.savedstate:savedstate-ktx:1.2.0", + "androidx.savedstate:savedstate:1.2.0", + "androidx.lifecycle:lifecycle-livedata-core-ktx:2.5.1", + "androidx.lifecycle:lifecycle-livedata-core:2.5.1", + "androidx.lifecycle:lifecycle-livedata:2.5.1", + "androidx.lifecycle:lifecycle-process:2.5.1", + "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1", + "androidx.lifecycle:lifecycle-runtime:2.5.1", + "androidx.lifecycle:lifecycle-service:2.5.1", + "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1", + "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1", + "androidx.lifecycle:lifecycle-viewmodel:2.5.1", + ], + repositories = [ + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) +use_repo(maven, "maven")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/README.md Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,11 @@ +# Android Jetpack Compose with Bazel example + +## Documentation + +For the full documentation, please visit +the [rules_kotlin documentation page](https://github.com/bazelbuild/rules_kotlin/blob/master/docs/kotlin.md). + +## Instructions + +1) Launch emulator +2) Run `bazel mobile-install //app/src/main:app --start_app`
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/WORKSPACE Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,42 @@ +# FIXME(alexeagle): move to bzlmod +workspace(name = "bazel_android_sample_project") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +_KOTLIN_COMPILER_VERSION = "1.7.20" + +_KOTLIN_COMPILER_SHA = "5e3c8d0f965410ff12e90d6f8dc5df2fc09fd595a684d514616851ce7e94ae7d" + +## Android + +http_archive( + name = "build_bazel_rules_android", + sha256 = "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", + strip_prefix = "rules_android-0.1.1", + urls = ["https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip"], +) + +load("@build_bazel_rules_android//android:rules.bzl", "android_sdk_repository") + +android_sdk_repository(name = "androidsdk") + +## Kotlin + +http_archive( + name = "io_bazel_rules_kotlin", + sha256 = "f033fa36f51073eae224f18428d9493966e67c27387728b6be2ebbdae43f140e", + url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v1.7.0-RC-3/rules_kotlin_release.tgz", +) + +load("@io_bazel_rules_kotlin//kotlin:repositories.bzl", "kotlin_repositories", "kotlinc_version") + +kotlin_repositories( + compiler_release = kotlinc_version( + release = _KOTLIN_COMPILER_VERSION, + sha256 = _KOTLIN_COMPILER_SHA, + ), +) + +load("@io_bazel_rules_kotlin//kotlin:core.bzl", "kt_register_toolchains") + +kt_register_toolchains()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/app/src/main/AndroidManifest.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.bazel"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/app/src/main/BUILD.bazel Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,27 @@ +load("@build_bazel_rules_android//android:rules.bzl", "android_binary") +load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_library") + +kt_android_library( + name = "lib", + srcs = ["java/com/example/android/bazel/MainActivity.kt"], + custom_package = "com.example.android.bazel", + manifest = "LibraryManifest.xml", + plugins = ["//:jetpack_compose_compiler_plugin"], + resource_files = glob(["res/**/*"]), + deps = [ + "@maven//:androidx_activity_activity_compose", + "@maven//:androidx_appcompat_appcompat", + "@maven//:androidx_compose_foundation_foundation", + "@maven//:androidx_compose_foundation_foundation_layout", + "@maven//:androidx_compose_runtime_runtime", + "@maven//:androidx_compose_ui_ui", + "@maven//:androidx_compose_ui_ui_tooling", + ], +) + +android_binary( + name = "app", + manifest = "AndroidManifest.xml", + manifest_values = {"applicationId": "com.example.android.bazel"}, + deps = [":lib"], +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/app/src/main/LibraryManifest.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.bazel"> + + <uses-sdk android:minSdkVersion="21" /> + + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/AppTheme"> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/app/src/main/java/com/example/android/bazel/MainActivity.kt Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,38 @@ +package com.example.android.bazel + +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { HelloWorld("Jetpack Compose") } + } + + @Preview + @Composable + fun HelloWorld(name: String) = Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxSize() + .padding(20.dp)) { + Text( + text = "Hello $name", + textAlign = TextAlign.Center + ) + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/app/src/main/res/drawable-v24/ic_launcher_foreground.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,34 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillType="evenOdd" + android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" + android:strokeColor="#00000000" + android:strokeWidth="1"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="78.5885" + android:endY="90.9159" + android:startX="48.7653" + android:startY="61.0927" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0" /> + <item + android:color="#00000000" + android:offset="1.0" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" + android:strokeColor="#00000000" + android:strokeWidth="1" /> +</vector>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/app/src/main/res/drawable/ic_launcher_background.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillColor="#26A69A" + android:pathData="M0,0h108v108h-108z" /> + <path + android:fillColor="#00000000" + android:pathData="M9,0L9,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,0L19,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,0L29,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,0L39,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,0L49,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,0L59,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,0L69,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,0L79,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M89,0L89,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M99,0L99,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,9L108,9" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,19L108,19" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,29L108,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,39L108,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,49L108,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,59L108,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,69L108,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,79L108,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,89L108,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,99L108,99" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,29L89,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,39L89,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,49L89,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,59L89,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,69L89,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,79L89,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,19L29,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,19L39,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,19L49,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,19L59,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,19L69,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,19L79,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> +</vector>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file
Binary file gara/android/jetpack-compose/app/src/main/res/mipmap-hdpi/ic_launcher_round.png has changed
Binary file gara/android/jetpack-compose/app/src/main/res/mipmap-mdpi/ic_launcher_round.png has changed
Binary file gara/android/jetpack-compose/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png has changed
Binary file gara/android/jetpack-compose/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png has changed
Binary file gara/android/jetpack-compose/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png has changed
Binary file gara/android/jetpack-compose/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/app/src/main/res/values/colors.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="colorPrimary">#3F51B5</color> + <color name="colorPrimaryDark">#303F9F</color> + <color name="colorAccent">#FF4081</color> +</resources>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/app/src/main/res/values/strings.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,3 @@ +<resources> + <string name="app_name">Built By Bazel</string> +</resources>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/jetpack-compose/app/src/main/res/values/styles.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,11 @@ +<resources> + + <!-- Base application theme. --> + <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> + <!-- Customize your theme here. --> + <item name="colorPrimary">@color/colorPrimary</item> + <item name="colorPrimaryDark">@color/colorPrimaryDark</item> + <item name="colorAccent">@color/colorAccent</item> + </style> + +</resources>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/.bazelrc Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,5 @@ +common --enable_workspace +common --experimental_google_legacy_api +common --experimental_enable_android_migration_apis +# Necesary until bazel 7.2.0rc2 or later is released (https://github.com/bazelbuild/bazel/issues/22415) +common --nocheck_visibility
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/.bazelversion Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,2 @@ +7.3.1 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/BUILD.bazel Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,15 @@ +platform( + name = "arm64-v8a", + constraint_values = [ + "@platforms//cpu:arm64", + "@platforms//os:android", + ], +) + +platform( + name = "x86", + constraint_values = [ + "@platforms//cpu:x86_32", + "@platforms//os:android", + ], +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/MODULE.bazel Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,30 @@ +"Bazel dependencies" + +bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_jvm_external", version = "5.3") +bazel_dep(name = "rules_cc", version = "0.0.9") +bazel_dep(name = "rules_android", version = "0.5.1") + +maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") +maven.install( + aar_import_bzl_label = "@rules_android//rules:rules.bzl", + artifacts = [ + "androidx.appcompat:appcompat:1.5.1", + "androidx.constraintlayout:constraintlayout:2.2.1", + # Needed to enforce version conflict resolution + "androidx.savedstate:savedstate:1.2.0", + "androidx.lifecycle:lifecycle-livedata-core:2.5.1", + "androidx.lifecycle:lifecycle-livedata:2.5.1", + "androidx.lifecycle:lifecycle-process:2.5.1", + "androidx.lifecycle:lifecycle-runtime:2.5.1", + "androidx.lifecycle:lifecycle-service:2.5.1", + "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1", + "androidx.lifecycle:lifecycle-viewmodel:2.5.1", + ], + repositories = [ + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], + use_starlark_android_rules = True, +) +use_repo(maven, "maven")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/README.md Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,24 @@ +# Android NDK with Bazel example + +## Documentation + +For the full documentation, please visit the [Bazel documentation page](https://bazel.build/docs/android-ndk). + +## Instructions + +1) Launch emulator +2) Run `bazel mobile-install //app/src/main:app --android_platforms=//:x86 --start_app` + +<img src="images/result.png" width="400px" /> + +## Build graph + + + +- JNI/C++ sources goes into the `cc_library` target, `//app/src/main:jni_lib`. +- Java sources, resource files, and assets go into the `android_library` + target, `//app/src/main:lib`. This target depends on the `cc_library` target. +- The APK is built from the `android_binary` target, `//app/src/main:app`. This + target depends on the `android_library` target. + +NOTE: This graph omits the Google Maven AAR dependencies.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/WORKSPACE Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,43 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +RULES_ANDROID_COMMIT = "93e27030d3f0defa39cbbc35195638cb772b0c27" + +http_archive( + name = "rules_android", + sha256 = "71cae2413868a24f17d43fd595af6f3905d2e5b3235f76514f54800bfd90c903", + strip_prefix = "rules_android-" + RULES_ANDROID_COMMIT, + urls = ["https://github.com/bazelbuild/rules_android/archive/%s.zip" % RULES_ANDROID_COMMIT], +) + +load("@rules_android//:prereqs.bzl", "rules_android_prereqs") + +rules_android_prereqs() + +load("@rules_android//:defs.bzl", "rules_android_workspace") + +rules_android_workspace() + +load("@rules_android//rules:rules.bzl", "android_sdk_repository") + +# Requires that the ANDROID_HOME environment variable is set to the Android SDK path. +android_sdk_repository( + name = "androidsdk", +) + +register_toolchains( + "@rules_android//toolchains/android:android_default_toolchain", + "@rules_android//toolchains/android_sdk:android_sdk_tools", +) + +http_archive( + name = "rules_android_ndk", + sha256 = "b1a5ddd784e6ed915c2035c0db536a278b5f50c64412128c06877115991391ef", + strip_prefix = "rules_android_ndk-877c68ef34c9f3353028bf490d269230c1990483", + url = "https://github.com/bazelbuild/rules_android_ndk/archive/877c68ef34c9f3353028bf490d269230c1990483.zip", +) + +load("@rules_android_ndk//:rules.bzl", "android_ndk_repository") + +android_ndk_repository(name = "androidndk") + +register_toolchains("@androidndk//:all")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/app/src/main/AndroidManifest.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.bazel" + > + + <uses-sdk android:minSdkVersion="23" /> + +</manifest>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/app/src/main/BUILD.bazel Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,27 @@ +load("@rules_android//android:rules.bzl", "android_binary", "android_library") +load("@rules_cc//cc:defs.bzl", "cc_library") + +android_library( + name = "lib", + srcs = ["java/com/example/android/bazel/MainActivity.java"], + custom_package = "com.example.android.bazel", + manifest = "LibraryManifest.xml", + resource_files = glob(["res/**/*"]), + deps = [ + ":jni_lib", + "@maven//:androidx_appcompat_appcompat", + "@maven//:androidx_constraintlayout_constraintlayout", + ], +) + +cc_library( + name = "jni_lib", + srcs = ["cpp/native-lib.cpp"], +) + +android_binary( + name = "app", + manifest = "AndroidManifest.xml", + manifest_values = {"applicationId": "com.example.android.bazel"}, + deps = [":lib"], +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/app/src/main/LibraryManifest.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.bazel" + > + + <uses-sdk android:minSdkVersion="23" /> + + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/AppTheme" + > + <activity + android:name=".MainActivity" + android:exported="true" + > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/app/src/main/cpp/native-lib.cpp Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,13 @@ +#include <jni.h> +#include <string> + +extern "C" +JNIEXPORT jstring + +JNICALL +Java_com_example_android_bazel_MainActivity_stringFromJNI( + JNIEnv *env, + jobject /* this */) { + std::string hello = "Hello from C++"; + return env->NewStringUTF(hello.c_str()); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/app/src/main/java/com/example/android/bazel/MainActivity.java Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,28 @@ +package com.example.android.bazel; + +import android.os.Bundle; +import android.widget.TextView; +import androidx.appcompat.app.AppCompatActivity; + +public class MainActivity extends AppCompatActivity { + + static { + System.loadLibrary("app"); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // Example of a call to a native method + TextView tv = (TextView) findViewById(R.id.sample_text); + tv.setText(stringFromJNI()); + } + + /** + * A native method that is implemented by the 'native-lib' native library, + * which is packaged with this application. + */ + public native String stringFromJNI(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/app/src/main/res/drawable-v24/ic_launcher_foreground.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,34 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillType="evenOdd" + android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" + android:strokeColor="#00000000" + android:strokeWidth="1"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="78.5885" + android:endY="90.9159" + android:startX="48.7653" + android:startY="61.0927" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0" /> + <item + android:color="#00000000" + android:offset="1.0" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" + android:strokeColor="#00000000" + android:strokeWidth="1" /> +</vector>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/app/src/main/res/drawable/ic_launcher_background.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillColor="#26A69A" + android:pathData="M0,0h108v108h-108z" /> + <path + android:fillColor="#00000000" + android:pathData="M9,0L9,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,0L19,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,0L29,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,0L39,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,0L49,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,0L59,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,0L69,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,0L79,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M89,0L89,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M99,0L99,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,9L108,9" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,19L108,19" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,29L108,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,39L108,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,49L108,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,59L108,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,69L108,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,79L108,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,89L108,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,99L108,99" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,29L89,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,39L89,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,49L89,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,59L89,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,69L89,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,79L89,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,19L29,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,19L39,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,19L49,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,19L59,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,19L69,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,19L79,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> +</vector>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/app/src/main/res/layout/activity_main.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="com.example.android.bazel.MainActivity" + > + + <TextView + android:id="@+id/sample_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Hello World!" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" + /> + +</androidx.constraintlayout.widget.ConstraintLayout>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/app/src/main/res/values/colors.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="colorPrimary">#3F51B5</color> + <color name="colorPrimaryDark">#303F9F</color> + <color name="colorAccent">#FF4081</color> +</resources>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/app/src/main/res/values/strings.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,3 @@ +<resources> + <string name="app_name">Built By Bazel</string> +</resources>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/ndk/app/src/main/res/values/styles.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,11 @@ +<resources> + + <!-- Base application theme. --> + <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> + <!-- Customize your theme here. --> + <item name="colorPrimary">@color/colorPrimary</item> + <item name="colorPrimaryDark">@color/colorPrimaryDark</item> + <item name="colorAccent">@color/colorAccent</item> + </style> + +</resources>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/BUILD.bazel Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,7 @@ +platform( + name = "arm64-v8a", + constraint_values = [ + "@platforms//cpu:arm64", + "@platforms//os:android", + ], +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/MODULE.bazel Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,22 @@ +"Bazel dependencies" + +bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_jvm_external", version = "5.3") + +maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") +maven.install( + artifacts = [ + "org.robolectric:robolectric:4.9", + "junit:junit:4.13.2", + "com.google.truth:truth:1.1.3", + "org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10", + "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10", + "org.jetbrains.kotlin:kotlin-stdlib:1.7.10", + ], + repositories = [ + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) +use_repo(maven, "maven")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/README.md Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,11 @@ +# Android Jetpack Compose with Bazel example + +## Documentation + +For the full documentation, please visit +the [robolectric-bazel documentation page](https://github.com/robolectric/robolectric-bazel#usage). + +## Instructions + +1) Launch emulator +2) Run `bazel test //app:test`
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/WORKSPACE Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,46 @@ +# FIXME(alexeagle): move to bzlmod +workspace(name = "bazel_android_sample_project") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +## Android + +http_archive( + name = "build_bazel_rules_android", + sha256 = "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", + strip_prefix = "rules_android-0.1.1", + urls = ["https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip"], +) + +load("@build_bazel_rules_android//android:rules.bzl", "android_sdk_repository") + +android_sdk_repository(name = "androidsdk") + +## Kotlin + +http_archive( + name = "io_bazel_rules_kotlin", + sha256 = "f033fa36f51073eae224f18428d9493966e67c27387728b6be2ebbdae43f140e", + url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v1.7.0-RC-3/rules_kotlin_release.tgz", +) + +load("@io_bazel_rules_kotlin//kotlin:repositories.bzl", "kotlin_repositories") + +kotlin_repositories() + +load("@io_bazel_rules_kotlin//kotlin:core.bzl", "kt_register_toolchains") + +kt_register_toolchains() + +# Android Testing + +http_archive( + name = "robolectric", + sha256 = "7655c49633ec85a18b5a94b1ec36e250671808e45494194959b1d1d7f3e73a23", + strip_prefix = "robolectric-bazel-4.9", + urls = ["https://github.com/robolectric/robolectric-bazel/archive/4.9.tar.gz"], +) + +load("@robolectric//bazel:robolectric.bzl", "robolectric_repositories") + +robolectric_repositories()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/app/BUILD.bazel Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,29 @@ +load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_library", "kt_android_local_test") + +kt_android_library( + name = "lib", + srcs = glob(["src/main/java/**/*.kt"]), + custom_package = "com.example.android.bazel", + manifest = "src/main/AndroidManifest.xml", + resource_files = glob(["src/main/res/**"]), + deps = [ + "@maven//:org_jetbrains_kotlin_kotlin_stdlib", + "@maven//:org_jetbrains_kotlin_kotlin_stdlib_common", + "@maven//:org_jetbrains_kotlin_kotlin_stdlib_jdk7", + "@maven//:org_jetbrains_kotlin_kotlin_stdlib_jdk8", + ], +) + +kt_android_local_test( + name = "test", + srcs = ["src/test/java/com/example/android/bazel/WelcomeActivityTest.kt"], + custom_package = "com.example.android.bazel.test", + test_class = "com.example.android.bazel.WelcomeActivityTest", + deps = [ + ":lib", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + "@maven//:org_robolectric_robolectric", + "@robolectric//bazel:android-all", + ], +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/app/src/main/AndroidManifest.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.bazel" + > + + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + > + + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <activity android:name=".LoginActivity" /> + </application> + +</manifest>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/app/src/main/java/com/example/android/bazel/LoginActivity.kt Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,11 @@ +package com.example.android.bazel + +import android.app.Activity +import android.os.Bundle + +class LoginActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/app/src/main/java/com/example/android/bazel/WelcomeActivity.kt Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,20 @@ +package com.example.android.bazel + +import android.app.Activity +import android.os.Bundle +import android.view.View +import android.content.Intent + +class WelcomeActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(R.layout.welcome_activity) + + val button: View = findViewById(R.id.login) + button.setOnClickListener({ v -> + startActivity(Intent(this@WelcomeActivity, LoginActivity::class.java)) + }) + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/app/src/main/res/drawable-v24/ic_launcher_foreground.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,34 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillType="evenOdd" + android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" + android:strokeColor="#00000000" + android:strokeWidth="1"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="78.5885" + android:endY="90.9159" + android:startX="48.7653" + android:startY="61.0927" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0" /> + <item + android:color="#00000000" + android:offset="1.0" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" + android:strokeColor="#00000000" + android:strokeWidth="1" /> +</vector>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/app/src/main/res/drawable/ic_launcher_background.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillColor="#26A69A" + android:pathData="M0,0h108v108h-108z" /> + <path + android:fillColor="#00000000" + android:pathData="M9,0L9,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,0L19,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,0L29,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,0L39,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,0L49,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,0L59,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,0L69,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,0L79,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M89,0L89,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M99,0L99,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,9L108,9" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,19L108,19" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,29L108,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,39L108,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,49L108,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,59L108,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,69L108,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,79L108,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,89L108,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,99L108,99" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,29L89,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,39L89,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,49L89,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,59L89,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,69L89,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,79L89,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,19L29,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,19L39,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,19L49,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,19L59,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,19L69,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,19L79,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> +</vector>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/app/src/main/res/layout/welcome_activity.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <Button + android:id="@+id/login" + android:text="Login" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + +</LinearLayout> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file
Binary file gara/android/robolectric-testing/app/src/main/res/mipmap-hdpi/ic_launcher.png has changed
Binary file gara/android/robolectric-testing/app/src/main/res/mipmap-hdpi/ic_launcher_round.png has changed
Binary file gara/android/robolectric-testing/app/src/main/res/mipmap-mdpi/ic_launcher.png has changed
Binary file gara/android/robolectric-testing/app/src/main/res/mipmap-mdpi/ic_launcher_round.png has changed
Binary file gara/android/robolectric-testing/app/src/main/res/mipmap-xhdpi/ic_launcher.png has changed
Binary file gara/android/robolectric-testing/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png has changed
Binary file gara/android/robolectric-testing/app/src/main/res/mipmap-xxhdpi/ic_launcher.png has changed
Binary file gara/android/robolectric-testing/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png has changed
Binary file gara/android/robolectric-testing/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png has changed
Binary file gara/android/robolectric-testing/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/app/src/main/res/values/strings.xml Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,3 @@ +<resources> + <string name="app_name">Built By Bazel</string> +</resources>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gara/android/robolectric-testing/app/src/test/java/com/example/android/bazel/WelcomeActivityTest.kt Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,29 @@ +package com.example.android.bazel + +import com.google.common.truth.Truth +import org.junit.Test +import org.robolectric.RobolectricTestRunner +import org.junit.runner.RunWith +import org.robolectric.Robolectric +import org.robolectric.Shadows.shadowOf +import android.content.Intent +import org.robolectric.RuntimeEnvironment +import android.app.Activity +import android.view.View +import org.junit.Assert.assertEquals + +@RunWith(RobolectricTestRunner::class) +class WelcomeActivityTest { + + @Test + fun clickingLogin_shouldStartLoginActivity() { + Robolectric.buildActivity(WelcomeActivity::class.java).use { controller -> + controller.setup() // Moves Activity to RESUMED state + val activity: Activity = controller.get() + activity.findViewById<View>(R.id.login).performClick() + val expectedIntent = Intent(activity, LoginActivity::class.java) + val actual: Intent = shadowOf(RuntimeEnvironment.application).getNextStartedActivity() + assertEquals(expectedIntent.getComponent(), actual.getComponent()) + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/react_games/src/2048/main.tsx Sat Dec 20 13:56:01 2025 -0500 @@ -0,0 +1,267 @@ +import React, { CSSProperties, useEffect, useReducer, useState } from "react"; + +/** + * 2048 + * + * 4 X 4 + */ + +const MAX_WIDTH = 4; + +type Color = 'white' | 'orange' | 'yellow' | 'red'; + +type Cell = { + value: number; + color: Color; +} + +type Board = Cell[][]; + +type GameState = "in_progress" | "lost" | "won"; + +type Game = { + board: Board; + state: GameState; + steps: number; +} + +type Command = "u" | "d" | "l" | "r"; + +type GameAction = + { type: "move", command: Command } | { type: "calculate" }; + + +interface GameStyle { + container: CSSProperties; + board: CSSProperties; + cell: (color: Color) => CSSProperties; +} + +const gameStyle: GameStyle = { + container: { + display: "flex", + flexDirection: "column", + justifyContent: "center", + alignItems: "center", + height: "100vh" + }, + board: { + display: "grid", + gridTemplateColumns: "repeat(4, 50px)", + background: "#EEFFEE", + }, + cell: (color: Color) => ({ + display: "flex", + justifyContent: "center", + alignItems: "center", + aspectRatio: "1 / 1 ", + margin: "10px", + background: color, + }) +} + +function initializeBoard(): Board { + const board = Array.from({ length: MAX_WIDTH }, () => + Array.from({ length: MAX_WIDTH }, (): Cell => ({ value: 0, color: 'orange' })) + ); + let rowIndex: number; + let colIndex: number; + rowIndex = Math.floor(Math.random() * 4); + colIndex = Math.floor(Math.random() * 4); + board[rowIndex][colIndex].value = 2; + board[rowIndex-1][colIndex].value = 2; + return board; +} + +function initializeGame(): Game { + return { + board: initializeBoard(), + state: "in_progress", + steps: 0, + } +} + + +function handleMove(board: Board, command: Command): Board { + // Deep copy the board and initialize the merged status for the new board + const copiedBoard = board.map(row => + row.map(cell => ({ ...cell, merged: false })) + ); + + let diff: { row: number, col: number }; + let startRow: number, endRow: number, stepRow: number; + let startCol: number, endCol: number, stepCol: number; + + const size = copiedBoard.length; + + switch (command) { + case "u": + diff = { row: -1, col: 0 }; + startRow = 0; endRow = size; stepRow = 1; + startCol = 0; endCol = size; stepCol = 1; + break; + case "d": + diff = { row: 1, col: 0 }; + startRow = size - 1; endRow = -1; stepRow = -1; + startCol = 0; endCol = size; stepCol = 1; + break; + case "l": + diff = { row: 0, col: -1 }; + startRow = 0; endRow = size; stepRow = 1; + startCol = 0; endCol = size; stepCol = 1; + break; + case "r": + diff = { row: 0, col: 1 }; + startRow = 0; endRow = size; stepRow = 1; + startCol = size - 1; endCol = -1; stepCol = -1; + break; + } + + for (let rowIndex = startRow; rowIndex !== endRow; rowIndex += stepRow) { + for (let colIndex = startCol; colIndex !== endCol; colIndex += stepCol) { + const currentCell = copiedBoard[rowIndex][colIndex]; + + if (currentCell.value === 0) continue; + + let r = rowIndex; + let c = colIndex; + let emptySlot: { r: number, c: number } = { r: rowIndex, c: colIndex }; + let finalSlot: { r: number, c: number } = { r: rowIndex, c: colIndex }; + + while (true) { + r += diff.row; + c += diff.col; + + if (r < 0 || r >= size || c < 0 || c >= size) { + finalSlot = emptySlot; + break; + } + + const nextCell = copiedBoard[r][c]; + + if (nextCell.value === 0) { + emptySlot = { r, c }; + finalSlot = emptySlot; + } else if (nextCell.value === currentCell.value && !nextCell.merged) { + finalSlot = { r, c }; + break; + } else { + finalSlot = emptySlot; + break; + } + } + + const targetCell = copiedBoard[finalSlot.r][finalSlot.c]; + + if (finalSlot.r === rowIndex && finalSlot.c === colIndex) { + continue; + } + + if (targetCell.value === currentCell.value && !targetCell.merged) { + targetCell.value *= 2; + targetCell.merged = true; + + copiedBoard[rowIndex][colIndex].value = 0; + + } else if (targetCell.value === 0) { + targetCell.value = currentCell.value; + copiedBoard[rowIndex][colIndex].value = 0; + } + } + } + + return copiedBoard; +} + +function addNewItemsToTheBoard(board: Board) { + let randomRowIndex: number; + let randomColIndex: number; + + + let zeroPos = 0; + board.forEach((row) => { + row.forEach((cell) => { + if (cell.value === 0) { + zeroPos += 1; + } + }) + }) + if (zeroPos === 0) { + return; + } + + let curr = 0; + const maxAddedValues = zeroPos < 2 ? 1 : (zeroPos / 2) | 0; + while (curr < maxAddedValues) { + randomRowIndex = Math.floor(Math.random() * board.length) + randomColIndex = Math.floor(Math.random() * board.length) + if (board[randomRowIndex][randomColIndex].value === 0) + { + board[randomRowIndex][randomColIndex].value = 2; + curr++; + } + } +} + +function gameDispatch(game: Game, gameAction: GameAction): Game { + switch(gameAction.type) { + case "move": { + const newBoard = handleMove(game.board, gameAction.command); + addNewItemsToTheBoard(newBoard); + return { + ...game, + board: newBoard, + } + } + case "calculate": { + return { + ...game, + } + } + } +} + +function Game() { + const [game, dispatch] = useReducer(gameDispatch, null, initializeGame); + + useEffect(() => { + window.addEventListener("keyup", (e) => { + switch(e.key) { + case "ArrowDown": { + dispatch({ type: "move", command: "d" }); + return; + } + case "ArrowUp": { + dispatch({ type: "move", command: "u" }); + return; + } + case "ArrowRight": { + dispatch({ type: "move", command: "r" }); + return; + } + case "ArrowLeft": { + dispatch({ type: "move", command: "l" }); + return; + } + default: + return; + } + }) + + }, []) + return ( + <div style={gameStyle.container}> + <h1> 2048 </h1> + <div style={gameStyle.board}> + {game.board.map((row: Cell[]) => { + return row.map((cell: Cell) => (<div style={gameStyle.cell(cell.color)}> {cell.value} </div>)) + })} + </div> + </div> + ); +} + + +export { + Game, +}
--- a/react_games/src/current.tsx Sat Dec 20 10:53:13 2025 -0800 +++ b/react_games/src/current.tsx Sat Dec 20 13:56:01 2025 -0500 @@ -1,265 +1,379 @@ -import { CSSProperties, useEffect, useReducer, useState } from "react"; +import { CSSProperties, useReducer, useState, useRef, useEffect } from "react"; import ReactDOM from "react-dom/client"; /** - * 2048 - * - * 4 X 4 + * CONFIGURATION + * Replace this with your actual API key or fetch it from an environment variable. */ - -const MAX_WIDTH = 4; - -type Color = 'white' | 'orange' | 'yellow' | 'red'; - -type Cell = { - value: number; - color: Color; -} - -type Board = Cell[][]; +const OPENAI_API_KEY = "YOUR_OPENAI_API_KEY_HERE"; -type GameState = "in_progress" | "lost" | "won"; - -type Game = { - board: Board; - state: GameState; - steps: number; -} - -type Command = "u" | "d" | "l" | "r"; - -type GameAction = - { type: "move", command: Command } | { type: "calculate" }; - - -interface GameStyle { - container: CSSProperties; - board: CSSProperties; - cell: (color: Color) => CSSProperties; +interface ChatStyle { + mainContainer: CSSProperties; + sideBar: CSSProperties; + sideBarItem: CSSProperties; // Added for hover/layout + mainChat: CSSProperties; + mainMessage: CSSProperties; + messageBubble: CSSProperties; // Added for styling messages + inputBar: CSSProperties; } -const gameStyle: GameStyle = { - container: { +const STYLES: ChatStyle = { + mainContainer: { + display: "flex", + backgroundColor: "#1e1e1e", + color: "white", + fontFamily: "sans-serif", + height: "100vh", + margin: 0, + }, + sideBar: { + width: "250px", + height: "100vh", + overflowY: "auto", + backgroundColor: "#000000", + borderRight: "1px solid #333", + padding: "1rem", + }, + sideBarItem: { + padding: "10px", + cursor: "pointer", + backgroundColor: "#333", + marginBottom: "5px", + borderRadius: "5px", + whiteSpace: "nowrap", + overflow: "hidden", + textOverflow: "ellipsis", + }, + mainChat: { + flex: 1, + display: "flex", + flexDirection: "column", + backgroundColor: "#343541", + height: "100vh", + }, + mainMessage: { + flex: 1, + padding: "20px", + overflowY: "auto", display: "flex", flexDirection: "column", - justifyContent: "center", - alignItems: "center", - height: "100vh" + gap: "15px", + }, + messageBubble: { + padding: "15px", + borderRadius: "8px", + lineHeight: "1.5", + maxWidth: "800px", + margin: "0 auto", + width: "100%", }, - board: { - display: "grid", - gridTemplateColumns: "repeat(4, 50px)", - background: "#EEFFEE", + inputBar: { + width: "100%", + height: "100px", + padding: "15px", + backgroundColor: "#40414f", + color: "white", + border: "none", + borderTop: "1px solid #555", + fontSize: "16px", + resize: "none", + outline: "none", }, - cell: (color: Color) => ({ - display: "flex", - justifyContent: "center", - alignItems: "center", - aspectRatio: "1 / 1 ", - margin: "10px", - background: color, - }) -} +}; + +type Chat = { + id: string; + title: string; + createdAt: number; +}; + +type ChatHistory = Chat[]; + +type Message = { + id: string; + role: 'user' | 'assistant'; // Changed from 'author' to match OpenAI spec usually + content: string; // Changed from 'message' to 'content' + createdAt: number; +}; + +type MainPage = { + activeChatId: string | null; + chatHistory: ChatHistory; + currentMessages: Message[]; + allChats: Record<string, Message[]>; + sendMessageStatus: 'idle' | 'inProgress' | 'failed' | 'success'; +}; + +// Expanded Actions +type MainPageAction = + | { type: 'select_chat'; payload: { chatId: string } } + | { type: 'user_message_sent'; payload: { content: string; newChatId?: string } } + | { type: 'api_response_received'; payload: { content: string } } + | { type: 'api_error'; payload: { error: string } }; + +function mainPageDispatch(state: MainPage, action: MainPageAction): MainPage { + switch (action.type) { + case 'select_chat': { + const { chatId } = action.payload; + return { + ...state, + activeChatId: chatId, + currentMessages: state.allChats[chatId] || [], + sendMessageStatus: 'idle', + }; + } -function initializeBoard(): Board { - const board = Array.from({ length: MAX_WIDTH }, () => - Array.from({ length: MAX_WIDTH }, (): Cell => ({ value: 0, color: 'orange' })) - ); - let rowIndex: number; - let colIndex: number; - rowIndex = Math.floor(Math.random() * 4); - colIndex = Math.floor(Math.random() * 4); - board[rowIndex][colIndex].value = 2; - board[rowIndex-1][colIndex].value = 2; - return board; -} + case 'user_message_sent': { + const { content, newChatId } = action.payload; + + const newMessage: Message = { + id: Date.now().toString(), + role: 'user', + content: content, + createdAt: Date.now(), + }; + + // If we are starting a brand new chat (no active ID) + if (newChatId && !state.activeChatId) { + const newChatMetadata: Chat = { + id: newChatId, + title: content.substring(0, 30) + (content.length > 30 ? "..." : ""), + createdAt: Date.now() + }; + + return { + ...state, + activeChatId: newChatId, + sendMessageStatus: 'inProgress', + chatHistory: [newChatMetadata, ...state.chatHistory], + currentMessages: [newMessage], + allChats: { + ...state.allChats, + [newChatId]: [newMessage] + } + }; + } + + const chatId = state.activeChatId!; + const updatedMessages = [...state.currentMessages, newMessage]; -function initializeGame(): Game { - return { - board: initializeBoard(), - state: "in_progress", - steps: 0, + return { + ...state, + sendMessageStatus: 'inProgress', + currentMessages: updatedMessages, + allChats: { + ...state.allChats, + [chatId]: updatedMessages + } + }; + } + + case 'api_response_received': { + if (!state.activeChatId) return state; + + const newMsg: Message = { + id: Date.now().toString(), + role: 'assistant', + content: action.payload.content, + createdAt: Date.now() + }; + + const updatedMessages = [...state.currentMessages, newMsg]; + + return { + ...state, + sendMessageStatus: 'success', + currentMessages: updatedMessages, + allChats: { + ...state.allChats, + [state.activeChatId]: updatedMessages + } + }; + } + + case 'api_error': { + return { + ...state, + sendMessageStatus: 'failed' + }; + } + + default: + return state; } } - -function handleMove(board: Board, command: Command): Board { - // Deep copy the board and initialize the merged status for the new board - const copiedBoard = board.map(row => - row.map(cell => ({ ...cell, merged: false })) - ); - - let diff: { row: number, col: number }; - let startRow: number, endRow: number, stepRow: number; - let startCol: number, endCol: number, stepCol: number; - - const size = copiedBoard.length; - - switch (command) { - case "u": - diff = { row: -1, col: 0 }; - startRow = 0; endRow = size; stepRow = 1; - startCol = 0; endCol = size; stepCol = 1; - break; - case "d": - diff = { row: 1, col: 0 }; - startRow = size - 1; endRow = -1; stepRow = -1; - startCol = 0; endCol = size; stepCol = 1; - break; - case "l": - diff = { row: 0, col: -1 }; - startRow = 0; endRow = size; stepRow = 1; - startCol = 0; endCol = size; stepCol = 1; - break; - case "r": - diff = { row: 0, col: 1 }; - startRow = 0; endRow = size; stepRow = 1; - startCol = size - 1; endCol = -1; stepCol = -1; - break; - } - - for (let rowIndex = startRow; rowIndex !== endRow; rowIndex += stepRow) { - for (let colIndex = startCol; colIndex !== endCol; colIndex += stepCol) { - const currentCell = copiedBoard[rowIndex][colIndex]; - - if (currentCell.value === 0) continue; - - let r = rowIndex; - let c = colIndex; - let emptySlot: { r: number, c: number } = { r: rowIndex, c: colIndex }; - let finalSlot: { r: number, c: number } = { r: rowIndex, c: colIndex }; - - while (true) { - r += diff.row; - c += diff.col; - - if (r < 0 || r >= size || c < 0 || c >= size) { - finalSlot = emptySlot; - break; - } - - const nextCell = copiedBoard[r][c]; - - if (nextCell.value === 0) { - emptySlot = { r, c }; - finalSlot = emptySlot; - } else if (nextCell.value === currentCell.value && !nextCell.merged) { - finalSlot = { r, c }; - break; - } else { - finalSlot = emptySlot; - break; - } - } - - const targetCell = copiedBoard[finalSlot.r][finalSlot.c]; - - if (finalSlot.r === rowIndex && finalSlot.c === colIndex) { - continue; - } - - if (targetCell.value === currentCell.value && !targetCell.merged) { - targetCell.value *= 2; - targetCell.merged = true; - - copiedBoard[rowIndex][colIndex].value = 0; - - } else if (targetCell.value === 0) { - targetCell.value = currentCell.value; - copiedBoard[rowIndex][colIndex].value = 0; - } - } - } - - return copiedBoard; -} - -function addNewItemsToTheBoard(board: Board) { - let randomRowIndex: number; - let randomColIndex: number; +const initialMainPage: MainPage = { + activeChatId: null, + chatHistory: [], + currentMessages: [], + allChats: {}, + sendMessageStatus: 'idle' +}; - let zeroPos = 0; - board.forEach((row) => { - row.forEach((cell) => { - if (cell.value === 0) { - zeroPos += 1; - } - }) - }) - if (zeroPos === 0) { - return; - } +async function fetchOpenAICompletion(messages: Message[]) { + const apiMessages = messages.map(m => ({ + role: m.role, + content: m.content + })); - let curr = 0; - const maxAddedValues = zeroPos < 2 ? 1 : (zeroPos / 2) | 0; - while (curr < maxAddedValues) { - randomRowIndex = Math.floor(Math.random() * board.length) - randomColIndex = Math.floor(Math.random() * board.length) - if (board[randomRowIndex][randomColIndex].value === 0) - { - board[randomRowIndex][randomColIndex].value = 2; - curr++; - } - } -} + try { + const response = await fetch("https://api.openai.com/v1/chat/completions", { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${OPENAI_API_KEY}` + }, + body: JSON.stringify({ + model: "gpt-3.5-turbo", // or gpt-4 + messages: apiMessages, + }) + }); -function gameDispatch(game: Game, gameAction: GameAction): Game { - switch(gameAction.type) { - case "move": { - const newBoard = handleMove(game.board, gameAction.command); - addNewItemsToTheBoard(newBoard); - return { - ...game, - board: newBoard, - } + if (!response.ok) { + throw new Error(`API Error: ${response.statusText}`); } - case "calculate": { - return { - ...game, - } - } + + const data = await response.json(); + return data.choices[0].message.content; + } catch (error) { + console.error(error); + throw error; } } function Current() { - const [game, dispatch] = useReducer(gameDispatch, null, initializeGame); + const [state, dispatch] = useReducer(mainPageDispatch, initialMainPage); + const [inputValue, setInputValue] = useState(""); + const messagesEndRef = useRef<HTMLDivElement>(null); + // Auto-scroll to bottom when messages change useEffect(() => { - window.addEventListener("keyup", (e) => { - switch(e.key) { - case "ArrowDown": { - dispatch({ type: "move", command: "d" }); - return; - } - case "ArrowUp": { - dispatch({ type: "move", command: "u" }); - return; - } - case "ArrowRight": { - dispatch({ type: "move", command: "r" }); - return; - } - case "ArrowLeft": { - dispatch({ type: "move", command: "l" }); - return; - } - default: - return; - } - }) + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [state.currentMessages]); + + const handleSendMessage = async () => { + if (!inputValue.trim()) return; + + // 1. Determine if this is a new chat or existing + const isNewChat = !state.activeChatId; + const currentChatId = state.activeChatId || crypto.randomUUID(); // Generate ID for new chat + + const textToSend = inputValue; + setInputValue(""); // Clear input immediately + + // 2. Dispatch User Message (Optimistic UI) + dispatch({ + type: 'user_message_sent', + payload: { content: textToSend, newChatId: isNewChat ? currentChatId : undefined } + }); + + try { + // 3. Prepare context (include previous messages + new one) + // Note: We reconstruct the array here because state update is async and might not be ready + const contextMessages: Message[] = [ + ...(isNewChat ? [] : state.currentMessages), + { id: 'temp', role: 'user', content: textToSend, createdAt: Date.now() } + ]; + + // 4. Call API + const aiResponse = await fetchOpenAICompletion(contextMessages); + + // 5. Dispatch Success + dispatch({ type: 'api_response_received', payload: { content: aiResponse } }); + + } catch (error) { + dispatch({ type: 'api_error', payload: { error: 'Failed to fetch' } }); + alert("Failed to connect to OpenAI. Check API Key."); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + }; - }, []) return ( - <div style={gameStyle.container}> - <h1> 2048 </h1> - <div style={gameStyle.board}> - {game.board.map((row: Cell[]) => { - return row.map((cell: Cell) => (<div style={gameStyle.cell(cell.color)}> {cell.value} </div>)) - })} + <div style={STYLES.mainContainer}> + {/* Sidebar */} + <div style={STYLES.sideBar}> + <h3 style={{color: '#ececf1', marginBottom: '20px'}}>History</h3> + <div + style={{...STYLES.sideBarItem, border: '1px dashed #555'}} + onClick={() => { + // Reset to empty view for a new chat + // We can achieve this by setting activeChatId to null locally if we wanted + // But for this simple reducer, we can just reload the page or add a 'reset' action. + // For now, let's just allow clicking existing ones. + window.location.reload(); + }} + > + + New Chat + </div> + + {state.chatHistory.map((hist) => ( + <div + key={hist.id} + style={{ + ...STYLES.sideBarItem, + backgroundColor: state.activeChatId === hist.id ? '#555' : '#333' + }} + onClick={() => dispatch({ type: 'select_chat', payload: { chatId: hist.id } })} + > + {hist.title} + </div> + ))} + </div> + + {/* Main Chat Area */} + <div style={STYLES.mainChat}> + <div style={STYLES.mainMessage}> + {state.currentMessages.length === 0 && ( + <div style={{color: '#666', textAlign: 'center', marginTop: '40%'}}> + Send a message to start... + </div> + )} + + {state.currentMessages.map((msg) => ( + <div + key={msg.id} + style={{ + ...STYLES.messageBubble, + backgroundColor: msg.role === 'assistant' ? '#444654' : 'transparent' + }} + > + <strong style={{color: msg.role === 'assistant' ? '#10a37f' : '#ececf1'}}> + {msg.role === 'assistant' ? 'AI' : 'You'}: + </strong> + <div style={{whiteSpace: 'pre-wrap', marginTop: '5px'}}>{msg.content}</div> + </div> + ))} + + {state.sendMessageStatus === 'inProgress' && ( + <div style={{...STYLES.messageBubble, color: '#888'}}>AI is typing...</div> + )} + <div ref={messagesEndRef} /> + </div> + + {/* Input Area */} + <textarea + style={STYLES.inputBar} + placeholder="Send a message..." + value={inputValue} + onChange={(e) => setInputValue(e.target.value)} + onKeyDown={handleKeyDown} + disabled={state.sendMessageStatus === 'inProgress'} + /> </div> </div> ); } ReactDOM.createRoot(document.getElementById("root")!).render(<Current />); + + +https://www.linkedin.com/in/drakewong/ +https://www.linkedin.com/in/dmitry-manannikov/