opencode-skills-collection 3.0.42 → 3.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/README.md +10 -0
  2. package/bundled-skills/.antigravity-install-manifest.json +3 -1
  3. package/bundled-skills/2slides-ppt-generator/SKILL.md +8 -18
  4. package/bundled-skills/accesslint-diff/SKILL.md +5 -2
  5. package/bundled-skills/android-dev/SKILL.md +524 -0
  6. package/bundled-skills/android-dev/references/flutter.md +269 -0
  7. package/bundled-skills/android-dev/references/hybrid.md +158 -0
  8. package/bundled-skills/android-dev/references/java-android.md +586 -0
  9. package/bundled-skills/android-dev/references/kmm.md +206 -0
  10. package/bundled-skills/android-dev/references/native-android.md +239 -0
  11. package/bundled-skills/android-dev/references/react-native.md +242 -0
  12. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  13. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  14. package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
  15. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  16. package/bundled-skills/docs/users/bundles.md +1 -1
  17. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  18. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  19. package/bundled-skills/docs/users/getting-started.md +1 -1
  20. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  21. package/bundled-skills/docs/users/usage.md +4 -4
  22. package/bundled-skills/docs/users/visual-guide.md +4 -4
  23. package/bundled-skills/event-staffing-ordering/SKILL.md +2 -17
  24. package/bundled-skills/unship/SKILL.md +138 -0
  25. package/package.json +1 -1
  26. package/skills_index.json +44 -0
@@ -0,0 +1,586 @@
1
+ # Native Android — Java Reference
2
+
3
+ ## When to Use Java
4
+
5
+ Java remains fully supported by Android and Google. Use it when:
6
+ - Maintaining or extending an existing Java codebase
7
+ - Team is Java-fluent without Kotlin experience
8
+ - Integrating Java-only SDKs or legacy modules
9
+ - Gradual migration: new Kotlin modules alongside old Java modules
10
+
11
+ > **Java + Kotlin interop is seamless** — you can have both in the same project. New files can be Kotlin while legacy files stay Java.
12
+
13
+ ---
14
+
15
+ ## Project Structure
16
+
17
+ ```
18
+ app/src/main/java/com/example/app/
19
+ ├── MyApp.java # Application class
20
+ ├── MainActivity.java # Host activity
21
+ ├── ui/
22
+ │ └── home/
23
+ │ ├── HomeActivity.java # OR Fragment-based
24
+ │ ├── HomeFragment.java
25
+ │ └── HomeAdapter.java
26
+ ├── viewmodel/
27
+ │ └── HomeViewModel.java
28
+ ├── repository/
29
+ │ └── ItemRepository.java
30
+ ├── data/
31
+ │ ├── remote/
32
+ │ │ ├── ApiService.java # Retrofit interface
33
+ │ │ ├── ApiClient.java # OkHttp + Retrofit setup
34
+ │ │ └── dto/ItemDto.java
35
+ │ └── local/
36
+ │ ├── AppDatabase.java # Room database
37
+ │ ├── ItemDao.java
38
+ │ └── entity/ItemEntity.java
39
+ ├── model/
40
+ │ └── Item.java # Domain model
41
+ └── di/ # Manual DI or Hilt
42
+ ```
43
+
44
+ ---
45
+
46
+ ## ViewModel (Java + LiveData)
47
+
48
+ ```java
49
+ public class HomeViewModel extends ViewModel {
50
+
51
+ private final MutableLiveData<UiState<List<Item>>> _uiState =
52
+ new MutableLiveData<>(UiState.loading());
53
+
54
+ public LiveData<UiState<List<Item>>> uiState = _uiState;
55
+
56
+ private final ItemRepository repository;
57
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
58
+
59
+ // Constructor injection (Hilt or manual)
60
+ public HomeViewModel(ItemRepository repository) {
61
+ this.repository = repository;
62
+ loadItems();
63
+ }
64
+
65
+ public void loadItems() {
66
+ _uiState.setValue(UiState.loading());
67
+ executor.execute(() -> {
68
+ try {
69
+ List<Item> items = repository.getItems();
70
+ _uiState.postValue(UiState.success(items));
71
+ } catch (Exception e) {
72
+ _uiState.postValue(UiState.error(e.getMessage()));
73
+ }
74
+ });
75
+ }
76
+
77
+ @Override
78
+ protected void onCleared() {
79
+ super.onCleared();
80
+ executor.shutdown();
81
+ }
82
+ }
83
+ ```
84
+
85
+ ---
86
+
87
+ ## UiState Wrapper
88
+
89
+ ```java
90
+ public class UiState<T> {
91
+ public enum Status { LOADING, SUCCESS, ERROR }
92
+
93
+ public final Status status;
94
+ public final T data;
95
+ public final String errorMessage;
96
+
97
+ private UiState(Status status, T data, String errorMessage) {
98
+ this.status = status;
99
+ this.data = data;
100
+ this.errorMessage = errorMessage;
101
+ }
102
+
103
+ public static <T> UiState<T> loading() {
104
+ return new UiState<>(Status.LOADING, null, null);
105
+ }
106
+
107
+ public static <T> UiState<T> success(T data) {
108
+ return new UiState<>(Status.SUCCESS, data, null);
109
+ }
110
+
111
+ public static <T> UiState<T> error(String message) {
112
+ return new UiState<>(Status.ERROR, null, message);
113
+ }
114
+
115
+ public boolean isLoading() { return status == Status.LOADING; }
116
+ public boolean isSuccess() { return status == Status.SUCCESS; }
117
+ public boolean isError() { return status == Status.ERROR; }
118
+ }
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Fragment Observing ViewModel
124
+
125
+ ```java
126
+ public class HomeFragment extends Fragment {
127
+
128
+ private HomeViewModel viewModel;
129
+ private FragmentHomeBinding binding; // ViewBinding
130
+
131
+ @Override
132
+ public View onCreateView(@NonNull LayoutInflater inflater,
133
+ ViewGroup container, Bundle savedInstanceState) {
134
+ binding = FragmentHomeBinding.inflate(inflater, container, false);
135
+ return binding.getRoot();
136
+ }
137
+
138
+ @Override
139
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
140
+ super.onViewCreated(view, savedInstanceState);
141
+
142
+ viewModel = new ViewModelProvider(this,
143
+ new HomeViewModelFactory(new ItemRepository(requireContext())))
144
+ .get(HomeViewModel.class);
145
+
146
+ viewModel.uiState.observe(getViewLifecycleOwner(), state -> {
147
+ binding.progressBar.setVisibility(state.isLoading() ? View.VISIBLE : View.GONE);
148
+ binding.recyclerView.setVisibility(state.isSuccess() ? View.VISIBLE : View.GONE);
149
+ binding.errorView.setVisibility(state.isError() ? View.VISIBLE : View.GONE);
150
+
151
+ if (state.isSuccess()) {
152
+ adapter.submitList(state.data);
153
+ }
154
+ if (state.isError()) {
155
+ binding.errorText.setText(state.errorMessage);
156
+ }
157
+ });
158
+
159
+ binding.retryButton.setOnClickListener(v -> viewModel.loadItems());
160
+ }
161
+
162
+ @Override
163
+ public void onDestroyView() {
164
+ super.onDestroyView();
165
+ binding = null; // CRITICAL — avoid memory leak
166
+ }
167
+ }
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Room Database (Java)
173
+
174
+ ```java
175
+ // Entity
176
+ @Entity(tableName = "items")
177
+ public class ItemEntity {
178
+ @PrimaryKey
179
+ @NonNull
180
+ public String id;
181
+ public String title;
182
+ public long updatedAt;
183
+
184
+ public ItemEntity(@NonNull String id, String title, long updatedAt) {
185
+ this.id = id;
186
+ this.title = title;
187
+ this.updatedAt = updatedAt;
188
+ }
189
+ }
190
+
191
+ // DAO
192
+ @Dao
193
+ public interface ItemDao {
194
+ @Query("SELECT * FROM items ORDER BY updatedAt DESC")
195
+ LiveData<List<ItemEntity>> observeAll();
196
+
197
+ @Query("SELECT * FROM items ORDER BY updatedAt DESC")
198
+ List<ItemEntity> getAll(); // blocking — call off main thread
199
+
200
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
201
+ void insertAll(List<ItemEntity> items);
202
+
203
+ @Query("DELETE FROM items")
204
+ void deleteAll();
205
+ }
206
+
207
+ // Database
208
+ @Database(entities = {ItemEntity.class}, version = 1, exportSchema = true)
209
+ public abstract class AppDatabase extends RoomDatabase {
210
+ private static volatile AppDatabase INSTANCE;
211
+
212
+ public abstract ItemDao itemDao();
213
+
214
+ public static AppDatabase getInstance(Context context) {
215
+ if (INSTANCE == null) {
216
+ synchronized (AppDatabase.class) {
217
+ if (INSTANCE == null) {
218
+ INSTANCE = Room.databaseBuilder(
219
+ context.getApplicationContext(),
220
+ AppDatabase.class,
221
+ "app_database"
222
+ ).build();
223
+ }
224
+ }
225
+ }
226
+ return INSTANCE;
227
+ }
228
+ }
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Retrofit API Client (Java)
234
+
235
+ ```java
236
+ // Interface
237
+ public interface ApiService {
238
+ @GET("items")
239
+ Call<List<ItemDto>> getItems();
240
+
241
+ @GET("items/{id}")
242
+ Call<ItemDto> getItemById(@Path("id") String id);
243
+
244
+ @POST("items")
245
+ Call<ItemDto> createItem(@Body ItemDto item);
246
+ }
247
+
248
+ // Client setup
249
+ public class ApiClient {
250
+ private static final String BASE_URL = BuildConfig.API_BASE_URL;
251
+ private static ApiService INSTANCE;
252
+
253
+ public static ApiService getInstance() {
254
+ if (INSTANCE == null) {
255
+ OkHttpClient client = new OkHttpClient.Builder()
256
+ .connectTimeout(10, TimeUnit.SECONDS)
257
+ .readTimeout(10, TimeUnit.SECONDS)
258
+ .addInterceptor(new AuthInterceptor())
259
+ .addInterceptor(new HttpLoggingInterceptor()
260
+ .setLevel(BuildConfig.DEBUG
261
+ ? HttpLoggingInterceptor.Level.BODY
262
+ : HttpLoggingInterceptor.Level.NONE))
263
+ .build();
264
+
265
+ Retrofit retrofit = new Retrofit.Builder()
266
+ .baseUrl(BASE_URL)
267
+ .client(client)
268
+ .addConverterFactory(GsonConverterFactory.create())
269
+ .build();
270
+
271
+ INSTANCE = retrofit.create(ApiService.class);
272
+ }
273
+ return INSTANCE;
274
+ }
275
+ }
276
+
277
+ // Auth interceptor
278
+ public class AuthInterceptor implements Interceptor {
279
+ @NonNull
280
+ @Override
281
+ public Response intercept(@NonNull Chain chain) throws IOException {
282
+ String token = TokenStorage.getInstance().getToken();
283
+ Request request = chain.request().newBuilder()
284
+ .addHeader("Authorization", "Bearer " + token)
285
+ .build();
286
+ return chain.proceed(request);
287
+ }
288
+ }
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Repository (Java)
294
+
295
+ ```java
296
+ public class ItemRepository {
297
+ private final ItemDao itemDao;
298
+ private final ApiService apiService;
299
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
300
+
301
+ public ItemRepository(Context context) {
302
+ AppDatabase db = AppDatabase.getInstance(context);
303
+ this.itemDao = db.itemDao();
304
+ this.apiService = ApiClient.getInstance();
305
+ }
306
+
307
+ // Synchronous fetch for ViewModel executor
308
+ public List<Item> getItems() throws Exception {
309
+ Response<List<ItemDto>> response = apiService.getItems().execute();
310
+ if (response.isSuccessful() && response.body() != null) {
311
+ return response.body().stream()
312
+ .map(ItemMapper::toDomain)
313
+ .collect(Collectors.toList());
314
+ } else {
315
+ throw new IOException("HTTP " + response.code());
316
+ }
317
+ }
318
+
319
+ // Observe cached data (returns LiveData — auto updates UI)
320
+ public LiveData<List<Item>> observeItems() {
321
+ return Transformations.map(itemDao.observeAll(), entities ->
322
+ entities.stream().map(ItemMapper::toDomain).collect(Collectors.toList())
323
+ );
324
+ }
325
+
326
+ // Refresh from network (call from background thread or executor)
327
+ public void refreshItems(Callback<Void> callback) {
328
+ executor.execute(() -> {
329
+ try {
330
+ Response<List<ItemDto>> response = apiService.getItems().execute();
331
+ if (response.isSuccessful() && response.body() != null) {
332
+ List<ItemEntity> entities = response.body().stream()
333
+ .map(ItemMapper::toEntity)
334
+ .collect(Collectors.toList());
335
+ itemDao.deleteAll();
336
+ itemDao.insertAll(entities);
337
+ callback.onSuccess(null);
338
+ } else {
339
+ callback.onError(new IOException("HTTP " + response.code()));
340
+ }
341
+ } catch (IOException e) {
342
+ callback.onError(e);
343
+ }
344
+ });
345
+ }
346
+
347
+ public interface Callback<T> {
348
+ void onSuccess(T result);
349
+ void onError(Exception e);
350
+ }
351
+ }
352
+ ```
353
+
354
+ ---
355
+
356
+ ## RecyclerView Adapter (Java)
357
+
358
+ ```java
359
+ public class ItemAdapter extends ListAdapter<Item, ItemAdapter.ItemViewHolder> {
360
+
361
+ private final OnItemClickListener listener;
362
+
363
+ public interface OnItemClickListener {
364
+ void onItemClick(Item item);
365
+ }
366
+
367
+ public ItemAdapter(OnItemClickListener listener) {
368
+ super(new DiffUtil.ItemCallback<Item>() {
369
+ @Override
370
+ public boolean areItemsTheSame(@NonNull Item a, @NonNull Item b) {
371
+ return a.getId().equals(b.getId());
372
+ }
373
+
374
+ @Override
375
+ public boolean areContentsTheSame(@NonNull Item a, @NonNull Item b) {
376
+ return a.equals(b);
377
+ }
378
+ });
379
+ this.listener = listener;
380
+ }
381
+
382
+ @NonNull
383
+ @Override
384
+ public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
385
+ ItemRowBinding binding = ItemRowBinding.inflate(
386
+ LayoutInflater.from(parent.getContext()), parent, false);
387
+ return new ItemViewHolder(binding);
388
+ }
389
+
390
+ @Override
391
+ public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
392
+ holder.bind(getItem(position), listener);
393
+ }
394
+
395
+ static class ItemViewHolder extends RecyclerView.ViewHolder {
396
+ private final ItemRowBinding binding;
397
+
398
+ ItemViewHolder(ItemRowBinding binding) {
399
+ super(binding.getRoot());
400
+ this.binding = binding;
401
+ }
402
+
403
+ void bind(Item item, OnItemClickListener listener) {
404
+ binding.titleText.setText(item.getTitle());
405
+ binding.getRoot().setOnClickListener(v -> listener.onItemClick(item));
406
+ }
407
+ }
408
+ }
409
+ ```
410
+
411
+ ---
412
+
413
+ ## XML Layout Best Practices (Java projects)
414
+
415
+ ```xml
416
+ <!-- Use ConstraintLayout — flat hierarchy = better performance -->
417
+ <androidx.constraintlayout.widget.ConstraintLayout
418
+ android:layout_width="match_parent"
419
+ android:layout_height="match_parent">
420
+
421
+ <!-- Always use ?attr/ tokens from MaterialTheme, never hardcoded colors -->
422
+ <TextView
423
+ android:id="@+id/titleText"
424
+ android:textColor="?attr/colorOnSurface"
425
+ android:textAppearance="?attr/textAppearanceTitleMedium"
426
+ android:layout_width="0dp"
427
+ android:layout_height="wrap_content"
428
+ app:layout_constraintStart_toStartOf="parent"
429
+ app:layout_constraintEnd_toEndOf="parent"
430
+ app:layout_constraintTop_toTopOf="parent" />
431
+
432
+ </androidx.constraintlayout.widget.ConstraintLayout>
433
+ ```
434
+
435
+ - Always use **ViewBinding** (not `findViewById`, not DataBinding for simple cases)
436
+ - Enable in `build.gradle.kts`: `viewBinding { enable = true }`
437
+ - Null `binding` in `onDestroyView()` to prevent Fragment memory leaks
438
+
439
+ ---
440
+
441
+ ## Error Handling (Java)
442
+
443
+ ```java
444
+ // Checked exceptions: always handle explicitly
445
+ public Result<List<Item>> getItemsSafe() {
446
+ try {
447
+ Response<List<ItemDto>> response = apiService.getItems().execute();
448
+ if (!response.isSuccessful()) {
449
+ return Result.failure(new HttpException(response));
450
+ }
451
+ List<Item> items = Objects.requireNonNull(response.body())
452
+ .stream().map(ItemMapper::toDomain).collect(Collectors.toList());
453
+ return Result.success(items);
454
+ } catch (IOException e) {
455
+ return Result.failure(new NetworkException("Network error", e));
456
+ } catch (NullPointerException e) {
457
+ return Result.failure(new ParseException("Empty response body", e));
458
+ }
459
+ }
460
+
461
+ // Custom exception hierarchy
462
+ public class AppException extends Exception {
463
+ public AppException(String message) { super(message); }
464
+ public AppException(String message, Throwable cause) { super(message, cause); }
465
+ }
466
+ public class NetworkException extends AppException { ... }
467
+ public class ParseException extends AppException { ... }
468
+ public class AuthException extends AppException { ... }
469
+ ```
470
+
471
+ ---
472
+
473
+ ## Hilt DI (Java)
474
+
475
+ ```java
476
+ // Application
477
+ @HiltAndroidApp
478
+ public class MyApp extends Application {}
479
+
480
+ // Activity / Fragment — annotate for injection
481
+ @AndroidEntryPoint
482
+ public class HomeFragment extends Fragment {
483
+ @Inject
484
+ ItemRepository repository; // injected by Hilt
485
+ }
486
+
487
+ // ViewModel
488
+ @HiltViewModel
489
+ public class HomeViewModel extends ViewModel {
490
+ private final ItemRepository repository;
491
+
492
+ @Inject
493
+ public HomeViewModel(ItemRepository repository) {
494
+ this.repository = repository;
495
+ }
496
+ }
497
+
498
+ // Module
499
+ @Module
500
+ @InstallIn(SingletonComponent.class)
501
+ public class DatabaseModule {
502
+ @Provides
503
+ @Singleton
504
+ public AppDatabase provideDatabase(@ApplicationContext Context context) {
505
+ return AppDatabase.getInstance(context);
506
+ }
507
+
508
+ @Provides
509
+ public ItemDao provideItemDao(AppDatabase db) {
510
+ return db.itemDao();
511
+ }
512
+ }
513
+ ```
514
+
515
+ ---
516
+
517
+ ## Unit Testing (Java)
518
+
519
+ ```java
520
+ @ExtendWith(MockitoExtension.class)
521
+ class HomeViewModelTest {
522
+
523
+ @Mock
524
+ ItemRepository mockRepository;
525
+
526
+ HomeViewModel viewModel;
527
+
528
+ @BeforeEach
529
+ void setup() {
530
+ viewModel = new HomeViewModel(mockRepository);
531
+ }
532
+
533
+ @Test
534
+ void loadItems_success_emitsSuccessState() throws Exception {
535
+ List<Item> items = Arrays.asList(new Item("1", "Test"));
536
+ when(mockRepository.getItems()).thenReturn(items);
537
+
538
+ viewModel.loadItems();
539
+
540
+ // Wait for executor — use CountDownLatch or InstantExecutorRule
541
+ UiState<List<Item>> state = viewModel.uiState.getValue();
542
+ assertNotNull(state);
543
+ assertTrue(state.isSuccess());
544
+ assertEquals(items, state.data);
545
+ }
546
+
547
+ @Test
548
+ void loadItems_failure_emitsErrorState() throws Exception {
549
+ when(mockRepository.getItems()).thenThrow(new IOException("Network error"));
550
+
551
+ viewModel.loadItems();
552
+
553
+ UiState<List<Item>> state = viewModel.uiState.getValue();
554
+ assertNotNull(state);
555
+ assertTrue(state.isError());
556
+ }
557
+ }
558
+ ```
559
+
560
+ ---
561
+
562
+ ## Java → Kotlin Migration Path
563
+
564
+ When migrating a Java project to Kotlin incrementally:
565
+
566
+ 1. **New files in Kotlin** — Java and Kotlin coexist seamlessly
567
+ 2. **Convert utilities first** — `@JvmStatic`, `@JvmField` for interop
568
+ 3. **Convert data models** — Java POJOs → Kotlin `data class`
569
+ 4. **Convert DAOs and Repositories** — add `suspend` + `Flow`
570
+ 5. **Convert ViewModels last** — swap `LiveData` + `MutableLiveData` for `StateFlow`
571
+ 6. **Convert Activities/Fragments** — migrate to Compose screen by screen
572
+ 7. Annotate Kotlin with `@JvmOverloads`, `@JvmName` where Java callers exist
573
+
574
+ ```kotlin
575
+ // Kotlin data class replacing a Java POJO
576
+ data class Item(
577
+ val id: String,
578
+ val title: String,
579
+ val updatedAt: Long = System.currentTimeMillis()
580
+ )
581
+
582
+ // Kotlin extension to consume Java LiveData from Kotlin cleanly
583
+ fun <T> LiveData<T>.observeNonNull(owner: LifecycleOwner, observer: (T) -> Unit) {
584
+ observe(owner) { it?.let(observer) }
585
+ }
586
+ ```