Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c109fb6
profile switcher button uses profile photo on mobile
jgw96 Mar 15, 2026
c223de4
localize
jgw96 Mar 15, 2026
97843c8
android capacitor
jgw96 Mar 15, 2026
d51e671
feat: add push notifications for Capacitor Android
jgw96 Mar 16, 2026
08de033
android icons
jgw96 Mar 16, 2026
53e2aa2
fix safe area issues
jgw96 Mar 16, 2026
8012e38
lint
jgw96 Mar 16, 2026
856732b
more safe area fixes
jgw96 Mar 16, 2026
b3e3d08
safe area fixes
jgw96 Mar 17, 2026
e120d0d
google play
jgw96 Mar 17, 2026
df046f1
Merge branch 'main' into profile-header-photo
jgw96 Mar 17, 2026
3cf3e15
fixed auth issue
jgw96 Mar 18, 2026
1008fa5
fix share
jgw96 Mar 18, 2026
8946849
trending widget
jgw96 Mar 18, 2026
8132de0
support android theme color
jgw96 Mar 18, 2026
10ead51
widget uses system theme color + native share target support
jgw96 Mar 18, 2026
e202ab6
shortcuts + predictive back
jgw96 Mar 18, 2026
b5b587b
fix predictive back
jgw96 Mar 19, 2026
0bae1c5
notification channels
jgw96 Mar 19, 2026
99fac0a
android wear app
jgw96 Mar 19, 2026
f702060
layout fixes + udpate deps + posting from the watch app
jgw96 Mar 20, 2026
b41126b
reply, threads and settings for the watch app + a sync to watch butto…
jgw96 Mar 21, 2026
57c7158
fix alt text generation on Android
jgw96 Mar 21, 2026
0364696
localize
jgw96 Mar 21, 2026
84733b5
improved haptics system
jgw96 Mar 21, 2026
2405af6
localize
jgw96 Mar 21, 2026
4c2f14c
bundle size back below cutoff
jgw96 Mar 21, 2026
1321999
Merge branch 'main' into profile-header-photo
jgw96 Mar 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ stats.html

# Service worker build artifact (generated from src/sw.ts)
public/sw.js

# Capacitor native build artifacts (projects themselves are committed)
android/app/build/
android/.gradle/
android/build/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for details.

- [Technical Architecture](docs/TECHNICAL_ARCHITECTURE.md)
- [Why Coho?](docs/WHY_COHO.md)
- [Safety Standards (CSAE)](SAFETY_STANDARDS.md)

<details>
<summary>AI Use Disclaimer</summary>
Expand Down
31 changes: 31 additions & 0 deletions SAFETY_STANDARDS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Safety Standards

## Child Sexual Abuse and Exploitation (CSAE) Policy

Coho is a fast, offline-first Mastodon client designed to connect users to the Fediverse. As a client application, Coho does not host any user-generated content directly. However, we are deeply committed to maintaining a safe environment and strictly prohibit any use of our application to facilitate, distribute, or access Child Sexual Abuse Material (CSAM) or any form of Child Sexual Abuse and Exploitation (CSAE).

### Zero Tolerance Policy

We have a zero-tolerance policy against CSAE. Any use of the Coho app to promote, facilitate, or share content related to the sexual abuse or exploitation of children is strictly prohibited.

### Content Moderation and Reporting

Because Coho connects to third-party Mastodon servers, content moderation is primarily handled by the administrators of those individual servers. However, Coho enforces safety at the client level by:

- **Blocking Known Offending Instances**: Coho reserves the right to block connections to any Mastodon instances that are known to host or tolerate CSAM or other forms of CSAE.
- **Reporting Mechanisms**: Users are encouraged to report any instances of CSAE encountered on the Fediverse. These reports should be directed to:
1. The administrator of the instance hosting the content.
2. The appropriate law enforcement agencies, such as the [National Center for Missing & Exploited Children (NCMEC)](https://www.missingkids.org/) in the United States, or local equivalent authorities.
3. Coho developers at jgw9617@gmail.com, so we may evaluate whether the offending instance should be disconnected or blocked from the client.

### Enforcement

Violations of this policy will result in immediate action, which may include:
- Blocking access to the offending instances from within the Coho app.
- Cooperating fully with law enforcement and reporting authorities in any investigations related to CSAE.

### User Responsibility

Users of Coho are expected to adhere to the rules and policies of the Mastodon instances they connect to, as well as local and international laws. Engaging in any activity involving CSAE is a severe criminal offense and will not be tolerated.

For more information on our community standards and how we ensure a safe environment, please refer to our [Code of Conduct](./CODE_OF_CONDUCT.md).
101 changes: 101 additions & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore

# Built application files
*.apk
*.aar
*.ap_
*.aab

# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/

# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml

# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/

# Google Services (e.g. APIs or Firebase)
# google-services.json

# Freeline
freeline.py
freeline/
freeline_project_description.json

# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md

# Version control
vcs.xml

# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/

# Android Profiling
*.hprof

# Cordova plugins for Capacitor
capacitor-cordova-android-plugins

# Copied web assets
app/src/main/assets/public

# Generated Config files
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml
2 changes: 2 additions & 0 deletions android/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/build/*
!/build/.npmkeep
66 changes: 66 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
apply plugin: 'com.android.application'

android {
namespace = "place.coho.app"
compileSdk = rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "place.coho.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 6
versionName "4.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

repositories {
flatDir{
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
}
}

dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation project(':capacitor-android')

// On-device AI: ML Kit Language Identification + Translation
implementation 'com.google.mlkit:language-id:17.0.6'
implementation 'com.google.mlkit:translate:17.0.3'

// On-device AI: ML Kit GenAI (Gemini Nano) – image description + proofreading
implementation 'com.google.mlkit:genai-image-description:1.0.0-beta1'
implementation 'com.google.mlkit:genai-proofreading:1.0.0-beta1'

// Wearable Data Layer (phone → watch credential sync)
implementation 'com.google.android.gms:play-services-wearable:19.0.0'

testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
implementation project(':capacitor-cordova-android-plugins')
}

apply from: 'capacitor.build.gradle'

try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}
22 changes: 22 additions & 0 deletions android/app/capacitor.build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN

android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
}

apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-app')
implementation project(':capacitor-browser')
implementation project(':capacitor-push-notifications')
implementation project(':capacitor-share')

}


if (hasProperty('postBuildExtras')) {
postBuildExtras()
}
29 changes: 29 additions & 0 deletions android/app/google-services.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "117311718455",
"project_id": "coho-mastodon",
"storage_bucket": "coho-mastodon.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:117311718455:android:ca27519307eab20e21ebb2",
"android_client_info": {
"package_name": "place.coho.app"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBu3u8WpBs5hkiLa6r3O8MKR-Mrd3xPWyg"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
21 changes: 21 additions & 0 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.getcapacitor.myapp;

import static org.junit.Assert.*;

import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {

@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();

assertEquals("com.getcapacitor.app", appContext.getPackageName());
}
}
Loading
Loading