Import GingerCal 1.0 source

This commit is contained in:
xocko committed 2026-10-15 16:35:05 +02:00
commit 01f49852ab
116 files changed
+5079

No files matched your search

+233
View File
@@ -0,0 +1,233 @@
package org.gingercal;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import javax.net.ssl.SSLException;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.gingercal.Model.Ev;
/** Shared helpers: settings, formatting, scheduling, dialogs. */
public final class App {
private App() {}
public static final int[] INTERVALS = {0, 1, 3, 6, 12, 24};
public static final int[] ALARMS = {-1, 0, 5, 10, 15, 30, 60, 120, 1440};
public static SharedPreferences prefs(Context c) {
return c.getSharedPreferences("gingercal", 0);
}
public static String get(Context c, String k) {
return prefs(c).getString(k, "");
}
public static boolean configured(Context c) {
return get(c, "url").length() > 0 && get(c, "user").length() > 0;
}
public static int interval(Context c) {
return prefs(c).getInt("interval", 3);
}
public static int remind(Context c) {
return prefs(c).getInt("remind", 15);
}
public static int ahead(Context c) {
return prefs(c).getInt("ahead", 60);
}
public static Dav dav(Context c) throws Exception {
return dav(get(c, "url"), get(c, "user"), get(c, "pass"), get(c, "pin"));
}
public static Dav dav(String url, String user, String pass, String pin) throws Exception {
return new Dav(new ApacheHttp(pin, 30000), url, user, pass);
}
public static boolean online(Context c) {
ConnectivityManager cm = (ConnectivityManager) c.getSystemService("connectivity");
NetworkInfo ni = cm == null ? null : cm.getActiveNetworkInfo();
return ni != null && ni.isConnected();
}
public static void requestSync(Context c, boolean manual, boolean force) {
Intent i = new Intent(c, SyncService.class);
i.putExtra("manual", manual);
i.putExtra("force", force);
c.startService(i);
}
/** (Re)arms the periodic background sync. */
public static void scheduleSync(Context c) {
AlarmManager am = (AlarmManager) c.getSystemService("alarm");
Intent i = new Intent(c, AlarmReceiver.class);
i.setAction(AlarmReceiver.SYNC);
PendingIntent pi = PendingIntent.getBroadcast(c, 1, i, 0);
am.cancel(pi);
int h = interval(c);
if (h > 0 && configured(c)) {
long every = h * ICal.HOUR;
am.setInexactRepeating(1 /* RTC: don't wake the phone just to sync */,
System.currentTimeMillis() + every, every, pi);
}
}
public static boolean isLan(String url) {
try {
String h = URI.create(Dav.normalize(url)).getHost();
if (h == null) return false;
h = h.toLowerCase(Locale.US);
if (h.startsWith("10.") || h.startsWith("192.168.") || h.equals("localhost") || h.endsWith(".local")
|| h.endsWith(".lan") || h.endsWith(".home.arpa")) return true;
if (h.startsWith("172.")) {
String[] p = h.split("\\.");
int b = Integer.parseInt(p[1]);
return b >= 16 && b <= 31;
}
if (h.startsWith("100.")) { // CGNAT / Tailscale-style ranges
int b = Integer.parseInt(h.split("\\.")[1]);
return b >= 64 && b <= 127;
}
} catch (RuntimeException ignored) {
}
return false;
}
// ------------------------------------------------------------ messages
public static String describe(Throwable t) {
if (t instanceof DavException || t instanceof DavException.UntrustedCert) return t.getMessage();
if (t instanceof UnknownHostException) {
return "Can't resolve " + t.getMessage() + ". No connection, or a typo in the server address.";
}
if (t instanceof ConnectException) return "Can't connect to the server (" + t.getMessage() + "). Wrong port, or the bridge is down?";
if (t instanceof SocketTimeoutException) return "The server took too long to answer.";
if (t instanceof SSLException) return "TLS error: " + t.getMessage();
if (t instanceof IOException) return "Network error: " + t;
if (t instanceof OutOfMemoryError) return "Out of memory. Lower 'Days ahead' in Settings.";
return "Error: " + t;
}
public static void toast(Context c, String s) {
Toast.makeText(c, s, 1).show();
}
public interface Trusted {
void trusted(String fingerprint);
}
public static void askTrust(Activity a, final DavException.UntrustedCert u, final Trusted cb) {
String msg = (u.pinMismatch
? "WARNING: the server certificate changed since you trusted it. That happens when you "
+ "regenerate the bridge certificate - or when someone is intercepting the connection.\n\n"
: "This phone doesn't trust the server certificate (normal for the self-signed legacy bridge, "
+ "or for Let's Encrypt on Android 2.3).\n\n")
+ "Subject: " + u.subject + "\nIssuer: " + u.issuer + "\nExpires: " + u.expires
+ "\n\nSHA-256:\n" + u.fingerprint
+ "\n\nOnly trust it if this matches the fingerprint printed on your server.";
new AlertDialog.Builder(a)
.setTitle(u.pinMismatch ? "Certificate changed!" : "Trust this certificate?")
.setMessage(msg)
.setPositiveButton("Trust", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {
cb.trusted(u.fingerprint);
}
})
.setNegativeButton("Cancel", null)
.show();
}
// ------------------------------------------------------------ formatting
public static String time(Context c, long t) {
String p = android.text.format.DateFormat.is24HourFormat(c) ? "HH:mm" : "h:mma";
return new SimpleDateFormat(p, Locale.getDefault()).format(new Date(t));
}
public static String day(long t) {
boolean thisYear = year(t) == year(System.currentTimeMillis());
return new SimpleDateFormat(thisYear ? "EEE d MMM" : "EEE d MMM yyyy", Locale.getDefault()).format(new Date(t));
}
static int year(long t) {
Calendar c = Calendar.getInstance();
c.setTimeInMillis(t);
return c.get(Calendar.YEAR);
}
public static String dayHeader(long dayStart) {
long today = ICal.startOfDay(System.currentTimeMillis());
if (dayStart == today) return "Today · " + day(dayStart);
if (dayStart == ICal.addDays(today, 1)) return "Tomorrow · " + day(dayStart);
return day(dayStart);
}
public static String when(Context c, Ev e) {
if (e.allDay) {
long lastDay = ICal.addDays(e.end, -1);
if (lastDay <= e.start) return "All day";
return "All day, until " + day(lastDay);
}
String s = time(c, e.start);
if (e.end <= e.start) return s;
if (ICal.startOfDay(e.end - 1) != ICal.startOfDay(e.start)) {
return s + " – " + day(e.end) + " " + time(c, e.end);
}
return s + " – " + time(c, e.end);
}
public static String alarmLabel(int m) {
if (m < 0) return "No reminder";
if (m == 0) return "At start time";
if (m < 60) return m + " min before";
if (m < 1440) return (m / 60) + " h before";
return (m / 1440) + " day before";
}
// --------------------------------------------------------------- views
public static int dp(Context c, float v) {
return (int) (v * c.getResources().getDisplayMetrics().density + 0.5f);
}
public static TextView text(Context c, String s, float sp, int color) {
TextView t = new TextView(c);
t.setText(s);
t.setTextSize(sp);
t.setTextColor(color);
return t;
}
public static LinearLayout vbox(Context c) {
LinearLayout l = new LinearLayout(c);
l.setOrientation(LinearLayout.VERTICAL);
return l;
}
public static int sysDrawable(Context c, String name) {
return c.getResources().getIdentifier(name, "drawable", "android");
}
}